```
# code cell
name = "Jonathan"
```

```
# code cell
name = "Jonathan"
```

```
# code cell
name = "Jonathan"
```

```
# Add this to python2 code to make life easier -- safe to use in python3 as well.
from __future__ import absolute_import, division, print_function
```

```
import numpy as np
# don't do:
# from numpy import *
```

```
max('a')
```

```
np.max('a')
```

```
%matplotlib inline
%config InlineBackend.figure_format='retina'
```

```
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os
from sklearn.datasets import load_boston
from pivottablejs import pivot_ui
import qgrid
import sys
import warnings
warnings.filterwarnings('ignore')
```

```
df = pd.read_csv("../data/mps.csv", encoding="ISO-8859-1")
```

```
# recommend using .head()
df.head(10)
```

```
# Province, Party, Average, Age, Heatmap
```

```
pivot_ui(df)
```

```
df_dict = load_boston()
features = pd.DataFrame(data=df_dict.data, columns = df_dict.feature_names)
target = pd.DataFrame(data=df_dict.target, columns = ['MEDV'])
df = pd.concat([features, target], axis=1)
```

```
df['Zone'] = df['ZN'].astype('category')
df['CHAS'] = df['CHAS'].astype('category')
df.head()
```

```
qgrid_widget = qgrid.show_grid(df[['CRIM',
                                   'Zone',
                                   'INDUS',
                                   'CHAS',
                                   'NOX',
                                   'AGE',
                                   'RAD',
                                   'LSTAT',
                                   'MEDV',
                                  ]], show_toolbar=True)
```

```
qgrid_widget
```

```
df2 = qgrid_widget.get_changed_df()
```

```
df2.head(7)
```

```
df.head(7)
```

```
import numpy as np
```

```
# shift-tab right ↓ more useful
np.linspace(start=50, endpoint=False, endpoint=100)
```

```
# shift-tab right ↓ more useful
np.linspace(start=50, endpoint=False, stop=100)
```

```
np.linspace?
```

```
?np.linspace
```

```
np.linspace??
```

```
!subl ~/miniconda3/envs/dspy3/lib/python3.6/site-packages/numpy/core/function_base.py
```

```
import textwrap
def example_function():
    """Docstring for example function"""

    print(textwrap.dedent("""
    This is a multi-lined string
    that I want to write inside of a function.
    Notice what happens when I print this.
        And when something is indented more."""))

example_function()
```

```
# But first find and replace
def silly_function(xval):
    """Takes a value and returns the value."""
    xval_sq = xval ** 2.0
    1 + 12
    xval_abs = np.sqrt(xval_sq)
    return xval_abs
```

```
silly_function?
```

```
silly_function??
```

```
silly_function??
```

```
# But first find and replace
def silly_function(xval):
    """Takes a value and returns the value."""
    xval_sq = xval ** 2.0
    1 + 10
    xval_abs = np.sqrt(xval_sq)
    return xval_abs
```

```
silly_function??
```

```
import textwrap
def example_function():
    """Docstring for example function"""

    print(textwrap.dedent("""
    This is a multi-lined string
    that I want to write inside of a function.
    Notice what happens when I print this.
        And when something is indented more."""))

example_function()
```

```
!ls ../data/
```

```
coal_years = !ls ../data/coal_prod_20*.csv
```

```
coal_years
```

```
example_dict = {}

# Indent/dedent/comment
for index in range(5):
    example_dict["one"] = 1
    example_dict["two"] = 2
    example_dict["three"] = 3
    example_dict["four"] = 4
```

```
example_dict["prepend_one_better_neat"] = 1
example_dict["prepend_two_better_neat"] = 2
example_dict["prepend_three_better_neat"] = 3
example_dict["prepend_four_better_neat"] = 4
```

```
example_dict["one_better_neat"] = 1
example_dict["two_better_neat"] = 2
example_dict["three_better_neat"] = 3
example_dict["four_better_neat"] = 4
```

```latex
%%latex

If you want to get crazier$\ldots$

\begin{equation}
\oint_S {E_n dA = \frac{1}{{\varepsilon _0 }}} Q_\textrm{inside}
\end{equation}
```

```bash
%%bash
wget http://www.ast.cam.ac.uk/~rfc/vpfit11.1.tar.gz
mkdir -p vpfit11
cd vpfit11
tar -xvzf ../vpfit11.1.tar.gz
```

```
normal_argument = 12.4
second_argument = 98.4

arg_with_spaces = "the secret to life"
```

```bash
%%bash -s {normal_argument} {second_argument}
echo "This script knows the value of the argument: $1"
echo "It also has no trouble with the second argument: $2"
```

```bash
%%bash -s "$arg_with_spaces"
echo "This bash script knows $1."
```

```
ls
```

```
!ls
```

```
%ls
```

```
tailthing = "*.ipynb"
```

```
!ls {tailthing}
```

```
output = !ls
```

```
output
```

```
%env
```

```
!pwd
```

```
a = 3
a
```

```
_i56
```

```
_56
```

```
_
```

```
b = 5
b * 15
```

```
_
```

```
c = a * b
```


# Bevezetés
Sykora Henrik - BME-GPK Műszaki Mechanikai Tanszék (sykora@mm.bme.hu)
### Python programozási nyelv

A Python egy open-source (OS), interpretált, általános célú programozási nyelv (vagy script-nyelv).

**Tulajdonságai:**
- Objektum orientált
- Interpretált
    - Nem szükséges fordítani (mint a pl a *C++*-t), elég csak beírni a parancsot, és már futtatható is a kód
    - Alkalmas teszi számítások gyors-prototipizálására
    - Cserébe lassú
- Open-source:
    - Ingyenes
    - Folyamatosan karban tartott
    - Széles körben felhasznált iparban és akadémiában is
    - Nagy "Community", sok segédlettel, fórummal (pl.: [stackoverflow](https://stackoverflow.com/questions/tagged/python))
- Moduláris:
    - Rengetek feladatra létezik "*package*" (pl.: numerikus számításokra *numpy*/*scipy*, szimbolikus számításokra *sympy*, táblázatfájl-kezelésre *CSV*)
    - Csak azt kell behívni, amire szükségünk van
    - Ismerni kell a *package* ekoszisztémát, mik léteznek, mi mire jó, stb...
- Sok IDE (*Integrated Development Environment*) létezik:
    - Alapvetően shell (terminál) alapú
    - Notebook: **_jupyter notebook_**, *jupyter lab*
    - Szövegszerkesztő: *Spyder*, *VS Code* (ingyenes/open source - ezek tartalmaznak *Debugger*-t is)
    - Fizetős szövegszerkeszők (lista nem teljes): *Visual Studio*, *PyCharm*, stb...

### Jupyter notebook működése (+ Python kernel):

Legfontosabb tudnivalók:

- Csak egy *front-end*, ami kommunikál egy *kernel*-lel (ez a kernel menüben választható).
- Két mód létezik:
    - Command mode (cellaműveleteket lehet végezni)
    - Edit mode (szövegbevitel cellába)
- Command mode (`ESC` billentyű lenyomásával érhető el, kék csík a cella kijelölése esetén):
    - Notebook mentése: `s`
    - Cellák hozzáadása: `b` az aktuális cella alá, `a` az aktuális cella fölé
    - Cella törlése: kétszer egymás után a `d` billentyű lenyomása
    - Cella törlésének visszavonása: `z`
    - Cella másolás: `c`, kivágás: `x`, beillesztés az aktuális cella alá: `v`
    - Számozás bekapcsolása a cella soraira: `l` (kis L), vagy `Shift + l` az összes cellára
    - Cellamódok: futtatandó kód: `y`, nyers kód (nem futtatható): `r`, markdown (formázott szöveg): `m`
- Edit mode (`Enter` billenytű lenyomásával érhető el, zöld szín):
    - Sor "kikommentelése"/"visszaállítása": `Ctrl + /`
    - Több kurzor lehelyezése: `Ctrl + Bal egérgomb`
    - Téglalap-szerű kijelölés (rectangular selection): `Alt + Bal egérgomb` "húzása" (dragging)
- Közös
    - Cella futtatása, majd cellaléptetés: `Shift + Enter` (ez létrehoz egy új cellát, ha nincs hova lépnie)
    - Cella futtatása cellaléptetés nélkül: `Ctrl + Enter`

**Jupyter notebook help-jének előhozása**: *Edit mode*-ban `h` lenyomásával
**Python help**: Kurzorral a függvény nevén állva `Shift + Tab` vagy egy cellába `?"fv_név"` beírása és futtatása

### A bevezető felépítése:
- [Alapműveletek](#Alapmuveletek)
- [Összetett függvények](#Osszetettfuggvenyek)
- [Saját függvények](#Sajatfuggvenyek)
- [Listák és vezérlési szerkezetek](#Vezerlesiszerkezetek)
- [Külső függvénykönyvtárak](#Kulsofuggvenykonyvtarak)
    - [Szimbolikus matematikai műveletek](#Szimbolikus)
    - [Deriválás/Integrálás](#DerivalIntegral)
    - [Vektor és mátrixszámítások Sympy-ban](#SzimVektorMatrix)
    - [Vektor és mátrixszámítások Numpy-ban](#NumVektorMatrix)
    - [Egyenletek megoldása Scipy-ban](#Egyenletek)
    - [Szimbolikus függvényekből numerikus függvények](#SymToNum)
- [Egyszerű ábrák készítése](#Egyszeruabrak)
- [+ Osztályok](#Osztalyok)

# Python bevezető

## Alapműveletek (Shift/Ctrl + Enter-rel futtassuk)

```python
17 + 7 #Összeadás
```

    24

```python
1+1
```

    2

```python
333 - 7 #Kivonás
```

    326

```python
11 * 22 #Szorzás
```

    242

```python
9/7 #Osztás (ez nem egész (int) lesz: külön típus float)
```

    1.2857142857142858

```python
9//7
```

    1

```python
0.3-0.1-0.2 # float: számábrázolási hiba lehet!!
```

    -2.7755575615628914e-17

```python
9**2 # Hatványozás (** és NEM ^!)
```

    81

```python
2**(0.5) # Gyökvönás hatványozás segítségével
```

    1.4142135623730951

```python
5e-3 #normálalak e segítségével (vagy 5E-3)
```

    0.005

Néhány alapművelet működik szövegre is

```python
'str1_' + 'str2_' #Összeadás
```

    'str1_str2_'

```python
2 * 'str2_' #Szorzás
```

    'str2_str2_'

## Összetettebb függvények

```python
sin(2) #szinusz
```

Összetettebb függvények már nincsenek a python alapnyelvben - ilyenkor szükséges behívni külső csomagokat, pl a **math** csomagot

```python
import math
```

```python
sin(2) # ez így továbbra sem létezik
```

```python
math.sin(2)
```

```python
# Több parancs együttes beírásakor nem látszik, csak az utolsó sor kimenete: print függvény alkalmazása!
print(math.sqrt(2))
print(math.tan(2))
print(math.atan(2))
```

    1.4142135623730951
    -2.185039863261519
    1.1071487177940904

```python
# Kimenet el is rejthető a ; segítségével ("suppress output")
1+1;
```

Amennyiben szükséges, definiálhatunk mi is saját változókat az `=` jellel.
Megjegyzés: a `=` értékadó függvénynek nincs kimenete

```python
a=2
b=3
c=4.0 # automatikus típusadás
```

```python
(a+b*c)**a # a legáltalánosabb típus lesz a kimenet (int < float)
```
# Fontos, hogy igyekezzük kerülni védett változó neveket! ILYET NE!
math.sqrt = 1
math.sqrt(2)

# KERNEL RESTART SZÜKSÉGES
**Ha véletlenül ilyet teszünk, akkor érdemes újraindítani a *kernel* a fent látható körkörös nyíllal, vagy a *Kernel* $\rightarrow$ *Restart* segítségével**

## Saját függvények

Szerkezet:
```python
def function(*arguments):
    instruction1
    instruction2
    ...
    return result
```

A függvény alá tartozó utasításokat tabulátoros behúzással (indent) kell beírni (nincs `{}` zárójel, vagy `end`). A függvény neve után jönnek az argumentumok majd kettősponttal `:` jelezzük, hogy hol kezdődik a függvény.

```python
def foo(x):
    return 3*x

def bar(x,y):
    a = x+y**2
    return 2*a + 4
```

```python
print(foo(3))
print(foo(3.))
print(foo('szöveg_'))

print(bar(3,4.))
```

    9
    9.0
    szöveg_szöveg_szöveg_
    42.0

Lehetséges úgynevezett anonim függvényeket (*anonymous function* vagy *lambda function*) is létrehozni, amely gyors módja az egyszerű, egysoros függvények létrehozására:

```python
lambda arguments: instruction
```

Ez akár egy változóhoz is hozzárendelhető, mint egy szám vagy string.

```python
double = lambda x : x*2
multiply = lambda x,y : x*y
```

```python
print(double(3))
print(multiply(10,3))
```

    6
    30

## Listák és vezérlési szerkezetek (Control Flow) - csak a legfontosabbak

### Listák

```python
lista = [1,2,3,4,"valami",[1.0,4]]
```

```python
print(lista[0]) # lista 1. eleme
print(lista[3]) # lista 4. eleme
print(lista[-1]) # negatív számokkal hátulról indexeljük a listát, és (-1)-től indul
print(lista[-2]) # lista utolsó előtti eleme
```

    1
    4
    [1.0, 4]
    valami

```python
print(lista[1:-1]) # egyszerre több elem [inkluzív:exklúzív módon]
print(lista[1:2]) # egyszerre több elem [inkluzív:exklúzív módon]
print(lista[2:]) # lista utolsó elemét is figyelembe vesszük
```

    [2, 3, 4, 'valami']
    [2]
    [3, 4, 'valami', [1.0, 4]]

```python
lista = [2,3,64,89,1,4,9,0,1]

lista.sort()
lista
```

### if-then-else

```python
if condition:
    instruction1
elif condition2:
    instruction2
else:
    intsturction3
```

```python
a=4
if a<=3:
    print('"a" nem nagyobb, mint 3')
elif a>=10:
    print('"a" nem kisebb, mint 10')
else:
    print('"a" nagyobb mint 3, de kisebb mint 10')
```

    "a" nagyobb mint 3, de kisebb mint 10

### for ciklus (for loop)
```python
for i in array:
    instruction
```

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

print()

for (i,elem) in enumerate(lista):
    print('lista ',i,'. eleme: ',elem,sep='') # több elem printelése egyszerr, szeparátor = ''

```

    0
    1
    2

    lista 0. eleme: 0
    lista 1. eleme: 1
    lista 2. eleme: 1
    lista 3. eleme: 2
    lista 4. eleme: 3
    lista 5. eleme: 4
    lista 6. eleme: 9
    lista 7. eleme: 64
    lista 8. eleme: 89

## Listák gyors létrehozása (List comprehension)

```python
lista2 = [3*i**2 for i in range(2,5)] # range: 2,3,4
lista2
```

```python
range(10000)
```

    range(0, 10000)

```python
lista3 = list(range(10))
lista3
```

```python
myfun = lambda x: 3*x**2

lista4 = [myfun(i) for i in range(2,10) if i%3 != 0] # ha i nem osztható 3-al
lista4
```

# Külső függvénykönyvtárak:

A mérnöki gyakorlatban előforduló alapszámításokhoz 2 fő csomag importálása ajánlott:
- `sympy`: szimbolikus számításokhoz
- `scipy`/`numpy`: numerikus számításokhoz (pl mátrix algebra)
- `matplotlib.pyplot`: Függvények ábrázolása
A `module`-ok hívása az alábbi módokon történhet
- `import` *`modulename`* : importálja az adott `module`-t. Ekkor a `module`-ban lévő függvények hívása a `module.functionname(arguments)` segítségével történik (Az *1_Alapok.ipynb*-ben láthattunk erre példát a `math` `module` esetében).
- `import` *`modulename`* `as` *`alias`* : hasonló az előzőhöz, de megválasztjuk, hogy milyen *alias*-ként hivatkozunk a `module`-unkra
- `from` *`modulename`* ` import` *`function1, function2, ...`* : csak bizonyos függvények importálása (nem szükséges a `module`-ra hivatkozni a függvények hívása során)
- `from` *`modulename`* ` import *` : a `module` összes függvényének importálása (nem szükséges a `module`-ra hivatkozni a függvények hívása során)

## Szimbolikus matematikai műveletek

```python
import math
import sympy as sp
import scipy as sc
sp.init_printing()
```

```python
F, m, a, b, c, x = sp.symbols("F m a b c x")
```

```python
F=m*a
```

```python
(a,7)
```

```python
F.subs(a,7)
```

```python
F.subs([(a,7),(m,1.1)])
```

```python
((a+b)**3).expand()
```

```python
((a+b)**7 - (b+2*a)**3).expand()
```

```python
(a**2+b**2+2*a*b).factor()
```

```python
sp.factor(a**2+b**2+2*a*b)
```

```python
sp.factor(b**3 + 3*a*b**2 + 3*a**2*b + a**3)
```

```python
a/b+c/b+7/b
```

```python
sp.ratsimp(a/b+c/b+7/b)
```

```python
(a/b+c/b+7/b).ratsimp()
```

```python
(sp.sin(x)**2 + sp.cos(x)**2).simplify().expand()
```

```python
(sp.cos(2*x)).expand()
```

```python
sp.expand_trig(sp.cos(2*x))
```

```python
import scipy.constants
```

```python
sc.constants.golden
```

```python
math.sqrt(-1+0j) # A math csomagban nincsenek implementálva a komplex számok!
```

```python
sc.sqrt(-1+0j)
```

```python
sp.limit(sp.sin(x)/x,x,0)
```

Taylor-sor megadása. Első paraméter a függvény, második a változó, harmadik az érték ami körül akarjuk a sort kifejteni, negyedik pedig a fokszám:

$$f\left(x\right) \approx \sum\limits_{i=0}^{N} \dfrac{\left(x - x_0\right)^i}{i!} \left.\dfrac{\mathrm{d}^i f}{\mathrm{d} x^i}\right|_{x = x_0}$$

```python
sp.series(sp.sin(x),x,0,20)
```

### Deriválás/Integrálás

```python
a,Δt,x = sp.symbols('a,Δt,x')
```

Deriválás

```python
sp.diff(sp.sin(x**3),x)
```

Többszöri deriválás

```python
sp.diff(sp.sin(x**3),x,3)
```

Integrálás

```python
sp.integrate(1/(1+x),x)
```

Határozott integrál

```python
sp.integrate(1/(1+x),(x,1,2))
```

```python
a = sp.Symbol('a')
```

```python
sp.integrate(1/(x**2 + a),x)
```

Láncszabályt is ismeri a szoftver

```python
y = sp.Symbol('y')
def f(x):
    return x**2
def g(y):
    return sp.sin(y)
```

```python
f(g(y))
```

```python
sp.diff(f(g(y)),y)
```

```python
sp.diff(g(f(x)),x)
```

Sok esetben nem létezik zárt alakú kifejezés a határozatlan integrálhoz. Ebben az esetben használhatjuk a [numerikus integrálást](https://en.wikipedia.org/wiki/Numerical_integration) a határozott integrál számításához:

```python
sp.integrate(sp.sin(sp.cos(x)),x)
```

[numerikus intágráls SymPy-vel](https://docs.sympy.org/latest/modules/integrals/integrals.html#numeric-integrals)

nem triviális

## Vektor- és mátrixszámítások *sympy*-ban
Kisebb méretek esetén, amelyek akár szimbolikus számokat is tartalmaznak

```python
v1= sp.Matrix([2.,3.,4.]) # oszlopvektor
v2= sp.Matrix([[3.,-2.,-7.]]) # sorvektor (még 1 szögletes zárójel)
mx1 = sp.Matrix([[1.,2.,3.],[2.,0.,4.],[3.,4.,1.]])
mx2 = sp.Matrix([[1.,2.,3.],[4.,5.,6.],[7.,8.,9.]])
EM = sp.eye(3) # egységmátrix
```

```python
v1
```

```python
v2
```

```python
v2.multiply(v1)
```

```python
mx2.multiply(v1)
```

```python
v2.multiply(mx2)
```

```python
EM
```

```python
EVs=mx1.eigenvals() # sajátértékek és multiplicitásuk (racionális számokkal)
```

```python
mx1.eigenvals(rational=False) # sajátértékek numerikusan
```

```python
list(mx1.eigenvals(rational=False)) # sajátértékek numerikusan, listaként (multiplicitás nélkül)
```

```python
mx1.det() # mx1 determinánsa
```

```python
Ix,Iy,Ixy = sp.symbols('Ix,Iy,Ixy')
mxSP=sp.Matrix([[Ix,-Ixy],[-Ixy,Iy]])
display(mxSP)

print('\n Sajátértékek, vektorok: \n')
mxSP.eigenvects()
```

```python
ev=mxSP.eigenvals()
```

```python
mxSP=sp.Matrix([[Ix,0,0],[0,Ix,0],[0,0,Iy]])
display(mxSP)

print('\n Sajátértékek, vektorok: \n')
mxSP.eigenvects()
```

## Vektor- és mátrixszámítások *numpy*-ban
Nagy mátrixok és vektorok esetén érdemes ezt használni, vagy ha sok numerikus adattal dolgozik az ember. Továbbá a sajátérték-sajátvektor számítások is jobban megoldott ebben a csomagban

```python
import sympy as sp
import numpy as np
import numpy.linalg
```

```python
v1= np.array([2.,3.,4.])
v2= np.array([3.,-2.,-7.])
mx1 = np.array([[1.,2.,3.],[2.,0.,4.],[3.,4.,1.]])
mx2 = np.array([[1.,2.,3.],[4.,5.,6.],[7.,8.,9.]])
```

```python
print( np.dot(mx2,v1) )  # skalár szorat mx2*v1
print( np.dot(v1,mx2) )  # skalár szorat transpose(v1)*mx2
print( np.cross(v1,v2) ) # keresztszorzat v1×v2
```

    [20. 47. 74.]
    [42. 51. 60.]
    [-13.  26. -13.]

```python
(λ,V) = np.linalg.eig(mx1) # Mátrix sajátértékei, sajátvektorai a λ és a V változókban

# kiíratás 2 értékes tizedes jeggyel és for ciklusra példa
for (i,v) in enumerate(V):
    print(i+1, '. sajátérték és -vektor:',sep='')
    print('λ = ', sp.N(λ[i],3), '; v = ', [sp.N(e,3) for e in v], sep='', end='\n\n')
```

    1. sajátérték és -vektor:
    λ = 6.76; v = [0.529, 0.834, -0.157]

    2. sajátérték és -vektor:
    λ = -1.15; v = [0.543, -0.475, -0.693]

    3. sajátérték és -vektor:
    λ = -3.61; v = [0.653, -0.281, 0.704]

Figyeljük meg, hogy a numpy a sajátvektorokat 1 hosszúre normálja!

## Egyenletek megoldása

```python
x, y, z = sp.symbols("x y z")
```

```python
egy1=77*x+6 - 160
```

```python
sp.solve(egy1,x)
```

```python
sp.solve(x**2 - 5, x)
```

```python
x
```

```python
e1 = 3*z + 2*y + 1*x - 7
e2 = 4*z + 0*y + 2*x - 8
e3 = 1*z + 4*y + 3*x - 9
```

```python
sp.solve([e1,e2,e3],[x,y,z])
```

```python
def f(x):
    return x**2 - 4
```

```python
# Numerikus gyökkereséssel scipy segítségével
from scipy import optimize

gyok = optimize.brentq(f,0,10)
print(gyok)
```

    2.0

### Szimbolikus függvényekből numerikus függvények

Ezt a legegyszerűbben [*lambda function*](#Sajatfuggvenyek)-ök segítségével tehetjük meg, de van más mód is rá: [lásd ezt az összefoglalót](https://docs.sympy.org/latest/modules/numeric-computation.html).

```python
a,b,c,x = sp.symbols('a,b,c,x')
adat = [(a, 3.0), (b,-2.), (c,1.0)]

masodfoku_x = a*x**2 + b*x + c

masodfoku_x
```

```python
masodfoku_num = sp.lambdify(x,masodfoku_x.subs(adat))
masodfoku_num(1.)
```

# Egyszerű ábrák készítése

```python
import numpy as np # Ez egyszerűsíti a sok függvénnyel való munkát
import matplotlib.pyplot as plt # matplotlib csomag python rajzolójának behívása
```

## Függvények ábrázolása egyszerű diagramokon

```python
def f(x):
    return np.sin(x)*(1-4*np.exp(0.7*x)/(x**3))

x1 = np.linspace(2,10,30)
y1 = f(x1)
```

```python
plt.figure(figsize=(20/2.54,12/2.54)) #inch-ben lehet megadni az ábra méretét - nem kötelező de érdemes, ha dokumentációba szánjuk

#plot objektum létrehozása
plt.plot(x1,y1)

# tengelyfeliratok
plt.xlabel('x',fontsize=12)
plt.ylabel('f(x)',fontsize=12)

# rácsozás
plt.grid()

# ábra mentés (még azelőtt kell, mielőtt kirajzolunk)
# plt.savefig("abra1.png", bbox_inches="tight")
# plt.savefig("abra1.pdf", bbox_inches="tight")

# plot kirajzolása
# plt.show()

# plot mentése
plt.savefig('abra1.png')  # lehet .pdf, .svg formátumokban is menteni
```

```python
plt.plot(x1,y1,linewidth=3,color='k',linestyle='-.') # vonalvastagság, -szín, és -stílus módosítása
plt.xlabel('x')
plt.ylabel('f(x)')
# megjelenítési határ
plt.ylim(-0.5,0.5) #-0.5...0.5
plt.xlim(None,8) # xmin...8
plt.grid()

plt.show()
```

```python
def g(x):
    return np.sqrt(x)
def h(x):
    return x**2

x2 = np.linspace(0.,1.,100)

plt.plot(x2,g(x2))
plt.plot(x2,h(x2))

plt.xlabel('x',fontsize=12)
# felirat
plt.legend((r'$\sqrt{x}$',r'$x^2$'), fontsize=12, loc = 5) # loc: 1:jobbfenn, 2: balfent, 3:ballent,...10
plt.grid()

plt.show()
```

```python
x3 = np.arange(1,14+1)
y3 = np.exp(x3)/(1.4e4)
plt.plot(x3, y3, linestyle = '', marker = 'o')
plt.xlabel('szorgalmi hét sorszáma')
plt.ylabel('hallgatók terheltsége [%]-ban')
plt.grid()
plt.show()
```

```python
plt.plot(x3, y3, linestyle = '-', marker = 'o')
plt.yscale('log')
plt.xlabel('szorgalmi hét sorszáma')
plt.ylabel(r'hallgatók terheltsége [%]-ban')
plt.grid()
plt.show()
```

## Parametrikus függvényábrázolás:

```python
t = np.linspace(0,2*sc.pi,100)
x = np.sin(t)
y = np.cos(t)

plt.plot(x, y, linestyle = '--')
plt.grid()
plt.axis('equal')
plt.show()
```

## 3D függvényábrázolás

```python
from mpl_toolkits.mplot3d import Axes3D
```

```python
def f(x, y):
    return np.sin(np.exp( - x ** 2 - y ** 2))

x = np.linspace(-2, 2, 30)
y = np.linspace(-2, 2, 30)

X, Y = np.meshgrid(x, y)
Z = f(X, Y)
```

```python
fig = plt.figure(figsize=(16/2.54,10/2.54)) # itt manuálisan létre kell hozni a plot (figure) objektumot
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis')
plt.show()
```

## + Osztályok

```python
def foo(x):
    return x**2

class MyClass:
    def __init__(self,x,y,z):
        self.square = foo(x)-z
        self.cubic = y**3+foo(y)

    @classmethod
    def createfrom_x(cls,x):
        return MyClass(x,x,x)

    def return_stuff(self):
        return self.square+3*self.cubic
```

```python
mcl=MyClass.createfrom_x(2)
```


# [Paris Saclay Center for Data Science](http://www.datascience-paris-saclay.fr)

## [RAMP](https://www.ramp.studio/problems/sea_ice) on arctic sea ice prediction

_Balázs Kégl (CNRS), Camille Marini (CNRS), Andy Rhines (UW), Jennifer Dy (NEU), Arindam Banerjee (UMN)_

## Introduction

Arctic sea ice cover is one of the most variable features of Earth's climate. Its annual cycle peaks at around 15 million square kilometers in early spring, melting back to a minimum of about 6 million square kilometers in September. These seasonal swings are important for Earth's energy balance, as ice reflects the majority of sunlight while open water absorbs it. Changes in ice cover are also important for marine life and navigation for shipping.

In recent years, Arctic sea ice cover has declined rapidly, particularly during the September minimum. These changes have outpaced the predictions of climate models, and forecasting extent remains a formidable challenge. Typically, skillful predictions are limited to ~2-5 months in advance [Stroeve, et al. "Improving Predictions of Arctic Sea Ice Extent"](https://eos.org/features/improving-predictions-of-arctic-sea-ice-extent), while idealized experiments suggest that predictions up to two years in advance should be possible [Guemas et al., 2014](http://onlinelibrary.wiley.com/doi/10.1002/qj.2401/abstract).

Better tools to predict ice cover are critical for seasonal and regional climate prediction, and would thus address grand challenges in the study of climate change ([World Climate Research Programme: Grand Challenges, 2013](http://wcrp-climate.org/grand-challenges)).

## The CCSM4 simulator

As a surrogate for observational data, we will use output from a 1300 year simulation using the NCAR [CCSM4.0](http://www.cesm.ucar.edu/models/ccsm4.0) climate model. The model was run in fully-coupled mode with interactive ocean, atmosphere, and sea ice. The simulation was also performed in an idealized "Pre-Industrial" mode, where greenhouse gas concentrations and other external forcings are held fixed to 1850 levels. This allows us to access a  stationary climate over a 1000+ year period, which makes the evaluation of the predictor more robust than if we used real measurements that are both non-stationary and limited to several decades.

## The data

The data is a time series of "images" $z_t$, consisting of different physical variables on a regular grid on the Earth, indexed by lon(gitude) and lat(itude) coordinates. The variables we have made available are:
* `ice_area`: the Northern Hemisphere sea ice area, in millions of squared kilometers.
* `ts`: surface temperature, most important over the oceans which have a very high heat capacity.
* `taux`: zonal (x-direction) surface wind stress. This is the frictional effect of winds on the sea surface and sea ice.
* `tauy`: meridional (y-direction) surface wind stress.
* `ps`: surface pressure.
* `psl`: equivalent sea-level surface pressure. This corrects ps for the effects of topography, though the two should be very similar.
* `shflx`: Surface sensible heat flux, the amount of heat transferred from the surface to the atmosphere.
* `cldtot`: Total cloud cover (fractional), which has strong effects on radiative energy balance at the surface.

The fields are recorded every month for 1300 years, giving 15,600 time points. The goal is to predict the Northern Hemisphere sea ice area 4 months ahead.

## The prediction task

The pipeline will consists of a time series feature extractor and a predictor. Since the task is regression, the predictor will be a regressor, and the score (to minimize) will be the [root mean square error](http://en.wikipedia.org/wiki/Root-mean-square_deviation). The feature extractor will have access to the whole data. It will construct a "classical" feature matrix where each row corresponds to a time point. You should collect all information into these features that you find relevant to the regressor. The feature extractor can take anything from the past, that is, it will implement a function $x_t = g(z_1, \ldots, z_t)$. Since you will have access to the full data, in theory you can cheat (even inadvertently) by using information from the future. We have implemented a randomized test to find such "bugs", but please do your best to avoid this since it would make the results irrelevant.

## Domain-knowledge suggestions

You are of course free to explore any regression technique to improve the prediction. Since the input dimension is relatively large (2000+ dimensions per time point even after subsampling) sparse regression techniques (eg. LASSO) may be the best way to go, but this is just an a priori suggestion. The following list provides you other hints to start with, based on domain knowledge.
* Some of the predictors will be very non-Gaussian.
* Teleconnections such as those associated with El Nino can be very important, so do not restrict your attention to variables in the Arctic.

# Exploratory data analysis

Packages to install:

conda install xarray dask netCDF4 basemap

```python
%matplotlib inline
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import xarray as xr
```

Let's start by reading the data into an xarray Dataset object. You can find all information on how to access and manipulate `Dataset` and `DataArray` objects at the [xarray site](http://xray.readthedocs.org/en/stable).

```python
X_ds = xr.open_dataset('data/train.nc')
y_array = np.load('data/train.npy')
```

[`xarray`](http://xarray.pydata.org/en/stable/time-series.html) uses `datetime64[ns]` as a time type which means that dates must be between 1678 and 2262. We convert whatever time type we have into `datetime64[ns]` starting at 1700. This only works if the monthly time series has length less than 562 years, which is the case of all train and test times series, both in the starting kit and in the backend. This is important so that, e.g., grouping by month works correctly.

```python
X_ds['time'] = pd.date_range(
    '1/1/1700', periods=X_ds['time'].shape[0], freq='M')\
    - np.timedelta64(15, 'D')
```

Printing it, you can see that it contains all the data, indices, and other metadata.

```python
X_ds
```

    <xarray.Dataset>
    Dimensions:   (lat: 39, lon: 58, time: 1800)
    Coordinates:
      * time      (time) datetime64[ns] 1700-01-16 1700-02-13 1700-03-16 ...
      * lat       (lat) float64 -90.0 -85.29 -80.58 -75.86 -71.15 -66.44 -61.73 ...
      * lon       (lon) float64 0.0 6.25 12.5 18.75 25.0 31.25 37.5 43.75 50.0 ...
    Data variables:
        ice_area  (time) float64 14.06 14.87 14.84 13.89 12.31 10.41 8.343 6.773 ...
        ts        (time, lat, lon) float32 245.185 245.122 245.106 245.114 ...
        taux      (time, lat, lon) float32 0.0119357 0.00967538 0.00755875 ...
        tauy      (time, lat, lon) float32 0.0177472 0.0183095 0.0190735 ...
        ps        (time, lat, lon) float32 69120.7 69120.7 69120.7 69120.7 ...
        psl       (time, lat, lon) float32 100224.0 100224.0 100224.0 100224.0 ...
        shflx     (time, lat, lon) float32 -1.9265 -2.07657 -2.13199 -2.11077 ...
        cldtot    (time, lat, lon) float32 0.217556 0.217556 0.217556 0.217556 ...
    Attributes:
        n_burn_in:    120
        n_lookahead:  4

`y_array` on the disk is already shifted by `n_lookahead = 4` months. `n_burn_in = 120` (months) is the length of the prefix for which no prediction is required. If your feature extractor only uses these ten years of the past to extract features from, you don't need to worry about missing data in the beginning of the sequence. Otherwise you should take care of the issue "manually" (handling missing data in the beginning of the sequence).

```python
y_array, y_array.shape
```

    (array([ 12.31275082,  10.41492558,   8.34265137, ...,  15.05521202,
             15.12392712,  14.23954105]), (1800,))

Past ice area is also part of the input, here you should be extremely careful not using it in the future.

```python
X_ds['ice_area']
```

    <xarray.DataArray 'ice_area' (time: 1800)>
    array([ 14.056189,  14.867679,  14.841535, ...,   8.755171,  10.52556 ,
            12.80698 ])
    Coordinates:
      * time     (time) datetime64[ns] 1700-01-16 1700-02-13 1700-03-16 ...
    Attributes:
        units:      M km^2
        long_name:  Northern hemisphere sea ice area

Let's plot all spatiotemporal input at a certain time point. Time is measured in number of month after the first point which represents a January.

```python
def plot_map(map_xr, time_index):

    lons, lats = np.meshgrid(map_xr['lon'], map_xr['lat'])

    fig = plt.figure()
    ax = fig.add_axes([0.05, 0.05, 0.9,0.9])
    map = Basemap(llcrnrlon=0, llcrnrlat=-89, urcrnrlon=360, urcrnrlat=89, projection='mill')
    # draw coastlines, country boundaries, fill continents.
    map.drawcoastlines(linewidth=0.25)
    #map.drawcountries(linewidth=0.25)
    #map.fillcontinents(color='coral',lake_color='aqua')
    # draw the edge of the map projection region (the projection limb)
    #map.drawmapboundary(fill_color='aqua')
    ax.set_title(map_xr.attrs['long_name'] + ' at month ' + str(time_index))
    im = map.pcolormesh(lons, lats, map_xr[time_index],
                        shading='flat', cmap=plt.cm.jet, latlon=True)
    cb = map.colorbar(im,"bottom", size="5%", pad="2%")

    #plt.savefig("test_plot.pdf")
    plt.show()
```

```python
t = 126
plot_map(X_ds['ts'], t)
plot_map(X_ds['taux'], t)
plot_map(X_ds['tauy'], t)
plot_map(X_ds['ps'], t)
plot_map(X_ds['psl'], t)
plot_map(X_ds['shflx'], t)
plot_map(X_ds['cldtot'], t)
```

## The target.

The size of the Northern hemisphere sea ice area in million square kilometers, shifted by 4 months. Note that this variable is also part of the input (see above), shifted back so it is aligned with the other variables.

```python
fig = plt.figure(figsize=(16, 12))
ax = fig.add_subplot(2, 1, 1)
ax.set_xlabel('time [months]')
ax.set_ylabel('ice area [M km^2]')
ax.plot(X_ds['time'], y_array)

ax = fig.add_subplot(2, 1, 2)
ax.set_xlabel('time [months]')
ax.set_ylabel('ice area [M km^2]')
ax.plot(X_ds['time'][:120], y_array[:120])
```

You can index xarray.DataArrays similarly to  pandas dataframes or numpy array. The result is always a <coda>DataArray

```python
t = 123
lat = 13
lon = 29
```

```python
X_ds['ice_area'][t]
```

    <xarray.DataArray 'ice_area' ()>
    array(15.104208946228027)
    Coordinates:
        time     datetime64[ns] 1710-04-15
    Attributes:
        units:      M km^2
        long_name:  Northern hemisphere sea ice area

```python
X_ds['ts'][t, lat]
```

    <xarray.DataArray 'ts' (lon: 58)>
    array([ 295.595917,  295.077911,  294.459259,  294.784088,  288.778809,
            291.563965,  298.488068,  297.634125,  297.555206,  297.538452,
            296.985779,  296.336151,  295.854889,  295.928864,  295.802582,
            295.874908,  295.745728,  295.77179 ,  296.79306 ,  292.079071,
            291.610535,  292.217377,  293.144531,  293.365082,  291.763672,
            297.667328,  296.720337,  296.548737,  296.528229,  296.442993,
            296.600342,  296.626129,  296.825836,  296.636566,  296.6008  ,
            296.299835,  296.133545,  296.138336,  296.254242,  296.246155,
            296.207275,  296.272217,  296.061768,  295.639862,  294.837006,
            293.281128,  292.998108,  291.669067,  295.59549 ,  293.546875,
            298.870697,  298.631134,  298.164978,  297.651123,  297.691742,
            297.313721,  296.688385,  296.033234], dtype=float32)
    Coordinates:
        time     datetime64[ns] 1710-04-15
        lat      float64 -28.74
      * lon      (lon) float64 0.0 6.25 12.5 18.75 25.0 31.25 37.5 43.75 50.0 ...
    Attributes:
        units:      K
        long_name:  Surface temperature (radiative)

You can convert any of these objects into a numpy array.

```python
X_ds['taux'].values
```

    array([[[  1.19356960e-02,   9.67537798e-03,   7.55875325e-03, ...,
               1.18619325e-02,   1.09429900e-02,   1.22338524e-02],
            [  4.41878587e-02,   3.38704921e-02,   3.16834785e-02, ...,
               6.33927882e-02,   5.51564693e-02,   4.85668816e-02],
            [  1.48713449e-02,   9.71391704e-03,   4.09940118e-03, ...,
               1.94972251e-02,   2.16817427e-02,   1.50534194e-02],
            ...,
            [  1.01837456e-01,   1.47577509e-01,   1.52642533e-01, ...,
              -7.09521398e-02,   2.52995710e-03,   5.99600412e-02],
            [  5.67872338e-02,   7.03624263e-02,   7.88953975e-02, ...,
               1.72229647e-03,   2.38575265e-02,   4.71802503e-02],
            [  7.66299218e-02,   7.68604428e-02,   7.69598857e-02, ...,
               7.20449910e-02,   7.48636052e-02,   7.62343481e-02]],

           [[  1.01154828e-02,   7.53489835e-03,   5.28584421e-03, ...,
               1.24596599e-02,   1.10400580e-02,   1.09038418e-02],
            [  2.70372722e-02,   1.82139259e-02,   1.79223008e-02, ...,
               5.91841228e-02,   4.45516631e-02,   3.35663259e-02],
            [  4.56722127e-03,  -2.30912329e-03,  -9.04263649e-03, ...,
               2.78667603e-02,   1.72966793e-02,   6.57241791e-03],
            ...,
            [  5.28127234e-03,   4.60262671e-02,   6.41433075e-02, ...,
              -1.09132707e-01,  -5.37769273e-02,  -2.30879579e-02],
            [ -8.29386618e-03,   1.45339733e-02,   3.09309326e-02, ...,
              -8.90958011e-02,  -5.65651134e-02,  -2.34235488e-02],
            [  3.85780595e-02,   4.43213768e-02,   4.99319695e-02, ...,
               2.18626168e-02,   2.95717269e-02,   3.52127962e-02]],

           [[  2.55916659e-02,   1.99487675e-02,   1.43996347e-02, ...,
               3.68869901e-02,   3.23505215e-02,   2.84630582e-02],
            [  9.83300284e-02,   7.80353844e-02,   6.21962585e-02, ...,
               1.62167504e-01,   1.39628366e-01,   1.13649629e-01],
            [  3.45649421e-02,   2.15536896e-02,   1.08636040e-02, ...,
               9.53203440e-02,   7.13457614e-02,   4.08168472e-02],
            ...,
            [ -2.83647515e-02,  -1.96722839e-02,  -1.37594007e-02, ...,
              -5.89759722e-02,  -3.41217369e-02,  -3.00275870e-02],
            [ -4.84686829e-02,  -3.73680145e-02,  -2.88465489e-02, ...,
              -8.75075683e-02,  -7.00048655e-02,  -5.63468076e-02],
            [  2.95577268e-03,   6.18538586e-03,   1.02356179e-02, ...,
              -6.64883293e-03,  -1.62067183e-03,   1.17990072e-03]],

           ...,
           [[  6.74928492e-03,   1.85473484e-03,  -3.03047406e-03, ...,
               1.85889266e-02,   1.40439617e-02,   9.40556638e-03],
            [  7.77858123e-02,   5.81281073e-02,   4.27184254e-02, ...,
               1.29711390e-01,   1.13835961e-01,   9.15294886e-02],
            [  1.81257632e-02,   3.50347464e-03,  -5.48585318e-03, ...,
               8.80435035e-02,   6.43973425e-02,   2.94387899e-02],
            ...,
            [  5.72110247e-03,   1.25772152e-02,   1.85365826e-02, ...,
              -4.72116061e-02,  -1.54972300e-02,  -2.24906858e-03],
            [  4.66008624e-03,   9.28835198e-03,   1.21561177e-02, ...,
              -1.80840958e-02,  -6.93337247e-03,   1.96989882e-03],
            [ -8.21887702e-03,  -8.70553963e-03,  -9.42833349e-03, ...,
              -6.50860276e-03,  -7.33792223e-03,  -7.90224969e-03]],

           [[  1.49074309e-02,   1.23018445e-02,   9.19409469e-03, ...,
               2.05269847e-02,   1.87849980e-02,   1.59909595e-02],
            [  5.99069074e-02,   4.49056588e-02,   3.19231674e-02, ...,
               9.68916565e-02,   8.56958330e-02,   6.94289878e-02],
            [  1.72058698e-02,   8.28679837e-03,   1.70873804e-03, ...,
               4.67530489e-02,   3.46728042e-02,   2.23747306e-02],
            ...,
            [ -3.31579112e-02,  -2.11932342e-02,  -1.32686114e-02, ...,
              -9.69535708e-02,  -4.63003963e-02,  -3.86148356e-02],
            [ -3.58242914e-02,  -1.93917193e-02,  -4.02707374e-03, ...,
              -1.03789270e-01,  -7.26778209e-02,  -4.67620790e-02],
            [  1.71900429e-02,   2.53514275e-02,   3.41531821e-02, ...,
              -3.92140355e-03,   5.31466538e-03,   1.26504060e-02]],

           [[  2.19966136e-02,   2.03868262e-02,   1.76709872e-02, ...,
               2.41104197e-02,   2.40787510e-02,   2.15190146e-02],
            [  3.91389951e-02,   3.04511730e-02,   2.07490362e-02, ...,
               5.02797216e-02,   5.13844006e-02,   4.45303731e-02],
            [ -1.92109519e-03,  -7.69918645e-03,  -1.27999941e-02, ...,
               1.12321945e-02,   5.19877812e-03,  -3.84666018e-05],
            ...,
            [  5.34604490e-02,   8.14611837e-02,   1.08710922e-01, ...,
              -2.82678455e-02,   1.20118940e-02,   3.67381312e-02],
            [ -4.74150642e-04,   3.57131270e-04,   1.99886650e-04, ...,
              -1.69663932e-02,  -8.53663310e-03,  -2.25929567e-03],
            [  1.48056466e-02,   1.55802239e-02,   1.66975856e-02, ...,
               1.25958174e-02,   1.38717657e-02,   1.44341793e-02]]], dtype=float32)

You can also use slices, and slice bounds don't even have to be in the index arrays.

```python
X_ds['tauy'][12].loc[10:50]
```

    <xarray.DataArray 'tauy' (lat: 8, lon: 58)>
    array([[ 0.049768,  0.046843,  0.095759, ...,  0.038427,  0.029995,  0.046069],
           [ 0.01939 ,  0.041139,  0.039579, ...,  0.032233,  0.034647,  0.035988],
           [ 0.023434,  0.00317 ,  0.037727, ...,  0.027224,  0.010083,  0.036892],
           ...,
           [-0.016391, -0.008304,  0.000831, ..., -0.053041, -0.041311, -0.039617],
           [-0.026566, -0.027404, -0.060934, ..., -0.098736, -0.113015, -0.319821],
           [-0.25397 , -0.374775, -0.134166, ..., -0.09506 , -0.117772, -0.080058]], dtype=float32)
    Coordinates:
        time     datetime64[ns] 1701-01-16
      * lat      (lat) float64 13.66 18.38 23.09 27.8 32.51 37.23 41.94 46.65
      * lon      (lon) float64 0.0 6.25 12.5 18.75 25.0 31.25 37.5 43.75 50.0 ...
    Attributes:
        units:      N/m2
        long_name:  Meridional surface stress

```python
X_ds.isel(time=t)
```

    <xarray.Dataset>
    Dimensions:   (lat: 39, lon: 58)
    Coordinates:
        time      datetime64[ns] 1710-04-15
      * lat       (lat) float64 -90.0 -85.29 -80.58 -75.86 -71.15 -66.44 -61.73 ...
      * lon       (lon) float64 0.0 6.25 12.5 18.75 25.0 31.25 37.5 43.75 50.0 ...
    Data variables:
        ice_area  float64 15.1
        ts        (lat, lon) float32 221.286 221.266 221.285 221.287 221.273 ...
        taux      (lat, lon) float32 0.0259886 0.0205801 0.0151063 0.00936647 ...
        tauy      (lat, lon) float32 0.0475492 0.0497843 0.0520096 0.0533593 ...
        ps        (lat, lon) float32 68001.4 68001.4 68001.4 68001.4 68001.4 ...
        psl       (lat, lon) float32 100270.0 100270.0 100270.0 100270.0 ...
        shflx     (lat, lon) float32 -18.782 -18.8291 -18.7908 -18.7875 -18.8209 ...
        cldtot    (lat, lon) float32 0.769741 0.769741 0.769741 0.769741 ...
    Attributes:
        n_burn_in:    120
        n_lookahead:  4

## The cross-validation object

Cross validating time-series predictors is tricky. We can't simply shuffle the observations $z_t =$ X_ds['tas'][t] since we would lose both causality and the correlation structure that follows natural order.

To formalize the issue, let us first define formally the predictor that we will produce in the RAMP. Let the time series be $z_1, \ldots, z_T$ and the let target to predict at time $t$ be $y_t$. The target is usually (and in our case) a function of the future $z_{t+1}, \ldots$, but it can be anything else. We want to learn a function that predicts $y$ from the past, that is

\begin{equation}
\hat{y}_t = f(z_1, ..., z_t) = f(Z_t)
\end{equation}

where $Z_t = (z_1, ..., z_t)$ is the past. Now, the sample $(Z_t, y_t)$ is a regular (although none iid) sample from the point of view of shuffling, so we can train on $\{Z_t, y_t\}_{t \in \cal{I}_{\text{train}}}$ and test on $(Z_t, y_t)_{t \in \cal{I}_{\text{test}}}$, where $\cal{I}_{\text{train}}$ and $\cal{I}_{\text{test}}$ are arbitrary but disjunct train and test index sets, respectively (typically produced by sklearn's `ShuffleSplit`). Using shuffling would nevertheless allow a second order leakage from training points to test points that preceed them, by, e.g., aggregating the training set and adding the aggregate back as a feature. To avoid this, we use block-CV: on each fold, all $t \in \cal{I}_{\text{test}}$ are larger than all $t \in \cal{I}_{\text{train}}$. We also make sure that all training and test sets contain consecutive observations, so recurrent nets and similar predictors, which rely on this, may be trained.

The training algorithm thus maps $(Z_t, y_t)_{t \in \cal{I}_{\text{train}}}$ to $f$. The point $Z_t$ contains the target for all training points $Z_{t'}$ for $t' \le t - 4$, so it is technically possible to cheat: when you receive a test set $z_1, ..., z_T$, you could look up the target of $z_t$ in $z_{t+4}$. To detect this (often inadvertant) cheating, we will check that you feature extractor is invariant to the future.

To allow a reasonably long past before making the first prediction, we strip the first $b = 120$ months (burn-in). You can of course use a longer window in your feature extractor, but in this case you will have to handle the missing time points in the beginning of the sequence.

## The pipeline

We have factorized the pipeline into two steps. The first feature extractor $g$ transforms the past into a classical feature vector $x_t = g(Z_t)$, and the classical regressor $h$ predicts the target from the feature vector $\hat{y}_t = h(x_t)$. To summarize, the full predictor is a composition $f(Z_t) = h(g(Z_t))$. If you have a complex solution where this factorization does not make sense (e.g., RNNs), you can do all the work in the (optional) fit function of the feature extractor, send the prediction as a single feature $x_t$ to the regressor, and simply use an identity function in the regressor $\hat{y}_t = x_t$.

### The feature extractor

The feature extractor implements a single `transform` function. As we explained above, it receives the full `X_ds` including the burn-in period $b$ as an attribute. It should produce a feature matrix of length $T - b$, of type numpy array, representing the past vector $(Z_{t+b}, \ldots, Z_{T})$. For constructing/computing $x_t$, it can only use the past $Z_t = (z_1, \ldots, z_t) = $ `X_ds['tas'][:t]`.

Note that the following code cells are *not* executed in the notebook. The notebook saves their contents in the file specified in the first line of the cell, so you can edit your submission before running the local test below and submitting it at the RAMP site.

```python
%%file submissions/starting_kit/ts_feature_extractor.py
import numpy as np

class FeatureExtractor(object):

    def __init__(self):
        pass

    def transform(self, X_ds):
        """Compute the vector of input variables at time t.

        Spatial variables will
        be averaged along lat and lon coordinates.
        """
        # This is the range for which features should be provided. Strip
        # the burn-in from the beginning and the prediction look-ahead from
        # the end.
        valid_range = np.arange(X_ds.n_burn_in, len(X_ds['time']))
        # We convert the Dataset into a 4D DataArray
        X_xr = X_ds.to_array()
        # We compute the mean over the lat and lon axes
        mean_xr = np.mean(X_xr, axis=(2, 3))
        # We convert it into numpy array, transpose, and slice the valid range
        X_array = mean_xr.values.T[valid_range]
        return X_array

```

    Overwriting submissions/starting_kit/ts_feature_extractor.py

### The regressor

The regressor should implement a scikit-klearn-like regressor with fit and predict functions. The starting kit uses a linear model.

```python
%%file submissions/starting_kit/regressor.py
from sklearn.base import BaseEstimator
from sklearn import linear_model

class Regressor(BaseEstimator):
    def __init__(self):
        self.reg = linear_model.LinearRegression()

    def fit(self, X, y):
        self.reg.fit(X, y)

    def predict(self, X):
        return self.reg.predict(X)

```

    Overwriting submissions/starting_kit/regressor.py

## Local testing (before submission)

It is important that you test your submission files before submitting them. For this we provide a unit test. Note that the test runs on your files in [`submissions/starting_kit`](/tree/submissions/starting_kit).

First `pip install ramp-workflow` or install it from the [github repo](https://github.com/paris-saclay-cds/ramp-workflow). Make sure that the python files `ts_feature_extractor.py` and  `regressor.py` are in the  [`submissions/starting_kit`](/tree/submissions/starting_kit) folder, and the data `train.nc` and `test.nc` are in [`data`](/tree/data). Then run

```ramp_test_submission```

If it runs and print training and test errors on each fold, then you can submit the code.

```python
!ramp_test_submission
```

    Testing Arctic sea ice forecast
    Reading train and test files from ./data ...
    Reading cv ...
    length of common block: 912 months = 76 years
    length of validation block: 768 months = 64 years
    length of each cv block: 96 months = 8 years
    Training ./submissions/starting_kit ...
    CV fold 0
    	train rmse = 0.904
    	valid rmse = 0.876
    	test rmse = 0.996
    CV fold 1
    	train rmse = 0.902
    	valid rmse = 0.862
    	test rmse = 0.978
    CV fold 2
    	train rmse = 0.909
    	valid rmse = 0.828
    	test rmse = 0.962
    CV fold 3
    	train rmse = 0.902
    	valid rmse = 0.81
    	test rmse = 0.964
    CV fold 4
    	train rmse = 0.889
    	valid rmse = 0.821
    	test rmse = 0.964
    CV fold 5
    	train rmse = 0.886
    	valid rmse = 0.815
    	test rmse = 0.969
    CV fold 6
    	train rmse = 0.893
    	valid rmse = 0.703
    	test rmse = 0.964
    CV fold 7
    	train rmse = 0.881
    	valid rmse = 0.674
    	test rmse = 0.958
    ----------------------------
    train rmse = 0.896 ± 0.0093
    valid rmse = 0.799 ± 0.0675
    test rmse = 0.97 ± 0.0115
    ----------------------------
    Testing if the notebook can be converted to html
    [NbConvertApp] Converting notebook ./sea_ice_starting_kit.ipynb to html
    [NbConvertApp] Writing 866271 bytes to ./sea_ice_starting_kit.html

Alternatively, load and execute `rampwf.utils.testing.py`, and call `assert_submission`. This may be useful if you would like to understand how we instantiate the workflow, the scores, the data connectors, and the cross validation scheme defined in [`problem.py`](problem.py), and how we insert and train/test your submission.

```python
# %load https://raw.githubusercontent.com/paris-saclay-cds/ramp-workflow/master/rampwf/utils/testing.py

"""The :mod:`rampwf.utils.testing` submodule provide utils to test ramp-kits"""
from __future__ import print_function

import imp
from os.path import join, abspath
from os import system

import numpy as np

def assert_read_problem(ramp_kit_dir='.'):
    problem = imp.load_source('', join(ramp_kit_dir, 'problem.py'))
    return problem

def assert_title(ramp_kit_dir='.'):
    problem = assert_read_problem(ramp_kit_dir)
    print('Testing {}'.format(problem.problem_title))

def assert_data(ramp_kit_dir='.', ramp_data_dir='.'):
    problem = assert_read_problem(ramp_kit_dir)
    print('Reading train and test files from {}/data ...'.format(
        ramp_data_dir))
    X_train, y_train = problem.get_train_data(path=ramp_data_dir)
    X_test, y_test = problem.get_test_data(path=ramp_data_dir)
    return X_train, y_train, X_test, y_test

def assert_cv(ramp_kit_dir='.', ramp_data_dir='.'):
    problem = assert_read_problem(ramp_kit_dir)
    X_train, y_train = problem.get_train_data(path=ramp_data_dir)
    print('Reading cv ...')
    cv = list(problem.get_cv(X_train, y_train))
    return cv

def assert_score_types(ramp_kit_dir='.'):
    problem = assert_read_problem(ramp_kit_dir)
    score_types = problem.score_types
    return score_types

def assert_submission(ramp_kit_dir='.', ramp_data_dir='.',
                      submission='starting_kit'):
    """Helper to test a submission from a ramp-kit.

    Parameters
    ----------
    ramp_kit_dir : str, (default='.')
        The directory of the ramp-kit to be tested for submission.

    ramp_data_dir : str, (default='.')
        The directory of the data

    submission_name : str, (default='starting_kit')
        The name of the submission to be tested.

    Returns
    -------
    None

    """
    problem = assert_read_problem(ramp_kit_dir)
    assert_title(ramp_kit_dir)
    X_train, y_train, X_test, y_test = assert_data(ramp_kit_dir, ramp_data_dir)
    cv = assert_cv(ramp_kit_dir, ramp_data_dir)
    score_types = assert_score_types(ramp_kit_dir)
    print('Training {}/submissions/{} ...'.format(
        ramp_kit_dir, submission))
    module_path = join(ramp_kit_dir, 'submissions', submission)
    train_train_scoress = np.empty((len(cv), len(score_types)))
    train_valid_scoress = np.empty((len(cv), len(score_types)))
    test_scoress = np.empty((len(cv), len(score_types)))
    for fold_i, (train_is, valid_is) in enumerate(cv):
        trained_workflow = problem.workflow.train_submission(
            module_path, X_train, y_train, train_is=train_is)

        y_pred_train = problem.workflow.test_submission(
            trained_workflow, X_train)
        predictions_train_train = problem.Predictions(
            y_pred=y_pred_train[train_is])
        ground_truth_train_train = problem.Predictions(
            y_true=y_train[train_is])
        predictions_train_valid = problem.Predictions(
            y_pred=y_pred_train[valid_is])
        ground_truth_train_valid = problem.Predictions(
            y_true=y_train[valid_is])

        y_pred_test = problem.workflow.test_submission(
            trained_workflow, X_test)
        predictions_test = problem.Predictions(y_pred=y_pred_test)
        ground_truth_test = problem.Predictions(y_true=y_test)

        print('CV fold {}'.format(fold_i))
        for score_type_i, score_type in enumerate(score_types):
            score = score_type.score_function(
                ground_truth_train_train, predictions_train_train)
            train_train_scoress[fold_i, score_type_i] = score
            print('\ttrain {} = {}'.format(
                score_type.name, round(score, score_type.precision)))

            score = score_type.score_function(
                ground_truth_train_valid, predictions_train_valid)
            train_valid_scoress[fold_i, score_type_i] = score
            print('\tvalid {} = {}'.format(
                score_type.name, round(score, score_type.precision)))

            score = score_type.score_function(
                ground_truth_test, predictions_test)
            test_scoress[fold_i, score_type_i] = score
            print('\ttest {} = {}'.format(
                score_type.name, round(score, score_type.precision)))

    print('----------------------------')
    means = train_train_scoress.mean(axis=0)
    stds = train_train_scoress.std(axis=0)
    for mean, std, score_type in zip(means, stds, score_types):
        print('train {} = {} ± {}'.format(
            score_type.name, round(mean, score_type.precision),
            round(std, score_type.precision + 1)))

    means = train_valid_scoress.mean(axis=0)
    stds = train_valid_scoress.std(axis=0)
    for mean, std, score_type in zip(means, stds, score_types):
        print('valid {} = {} ± {}'.format(
            score_type.name, round(mean, score_type.precision),
            round(std, score_type.precision + 1)))

    means = test_scoress.mean(axis=0)
    stds = test_scoress.std(axis=0)
    for mean, std, score_type in zip(means, stds, score_types):
        print('test {} = {} ± {}'.format(
            score_type.name, round(mean, score_type.precision),
            round(std, score_type.precision + 1)))

    print('----------------------------')
    problem_name = abspath(ramp_kit_dir).split('/')[-1]
    print('Testing if the notebook can be converted to html')
    system('jupyter nbconvert --to html {}/{}_starting_kit.ipynb'.format(
        ramp_kit_dir, problem_name))

```

```python
# assert_submission()
```

## Submitting to [ramp.studio](http://ramp.studio)

Once you found a good model, you can submit it to [ramp.studio](http://www.ramp.studio). First, if it is your first time using RAMP, [sign up](http://www.ramp.studio/sign_up), otherwise [log in](http://www.ramp.studio/login). Then find an open event on the particular problem, for example, the event [sea ice colorado](https://www.ramp.studio/events/sea_ice_colorado) for this RAMP. Sign up for the event. Both signups are controled by RAMP administrators, so there **can be a delay between asking for signup and being able to submit**.

Once your signup request is accepted, you can go to your [sandbox](http://www.ramp.studio/events/sea_ice_colorado/sandbox) and copy-paste (or upload) [`ts_feature_extractor.py`](/edit/submissions/starting_kit/ts_feature_extractor.py) and [`regressor.py`](/edit/submissions/starting_kit/regressor.py) from `submissions/starting_kit`. Save it, rename it, then submit it. The submission is trained and tested on our backend in the same way as `ramp_test_submission` does it locally. While your submission is waiting in the queue and being trained, you can find it in the "New submissions (pending training)" table in [my submissions](http://www.ramp.studio/events/sea_ice_colorado/my_submissions). Once it is trained, you get a mail, and your submission shows up on the [public leaderboard](http://www.ramp.studio/events/sea_ice_colorado/leaderboard).
If there is an error (despite having tested your submission locally with `ramp_test_submission`), it will show up in the "Failed submissions" table in [my submissions](http://www.ramp.studio/events/sea_ice_colorado/my_submissions). You can click on the error to see part of the trace.

After submission, do not forget to give credits to the previous submissions you reused or integrated into your submission.

The data set we use at the backend is usually different from what you find in the starting kit, so the score may be different.

The usual way to work with RAMP is to explore solutions, add feature transformations, select models, perhaps do some AutoML/hyperopt, etc., _locally_, and checking them with `ramp_test_submission`. The script prints mean cross-validation scores
```
----------------------------
train rmse = 0.896 ± 0.0093
valid rmse = 0.799 ± 0.0675
test rmse = 0.97 ± 0.0115
```
The official score in this RAMP (the first score column after "historical contributivity" on the [leaderboard](http://www.ramp.studio/events/sea_ice_colorado/leaderboard)) is root mean squared error ("rmse"), so the line that is relevant in the output of `ramp_test_submission` is `valid rmse = 0.799 ± 0.0675`. When the score is good enough, you can submit it at the RAMP.

## Other models in the starting kit

To get you started, we made several other example submissions.

This one uses the whole set of fields at time $t$ as the feature vector. We also modify the regressor because of the large dimensional input.

```python
%%file submissions/whole_fields/ts_feature_extractor.py
import numpy as np

class FeatureExtractor(object):

    def __init__(self):
        pass

    def transform(self, X_ds):
        """Compute the vector of input variables at time t.

        Spatial variables will be concatenated.
        """
        # This is the range for which features should be provided. Strip
        # the burn-in from the beginning and the prediction look-ahead from
        # the end.
        valid_range = np.arange(X_ds.n_burn_in, len(X_ds['time']))
        # We convert the Dataset into a 4D DataArray
        X_xr = X_ds.to_array()
        # We convert it into np array, put the t axis first
        X_array_t_first = np.swapaxes(X_xr.values, 0, 1)
        shape = X_array_t_first.shape
        # We reshape it to create one vector per time step, and slice the
        # valid range
        X_array = X_array_t_first.reshape(
            shape[0], shape[1] * shape[2] * shape[3])[valid_range]
        return X_array

```

    Overwriting submissions/whole_fields/ts_feature_extractor.py

```python
%%file submissions/whole_fields/regressor.py
from sklearn.base import BaseEstimator
from sklearn import linear_model

class Regressor(BaseEstimator):
    def __init__(self):
        self.reg = linear_model.Lasso()

    def fit(self, X, y):
        self.reg.fit(X, y)

    def predict(self, X):
        return self.reg.predict(X)

```

    Overwriting submissions/whole_fields/regressor.py

You can test this by

```python
!ramp_test_submission --submission whole_fields
```

    Testing Arctic sea ice forecast
    Reading train and test files from ./data ...
    Reading cv ...
    length of common block: 912 months = 76 years
    length of validation block: 768 months = 64 years
    length of each cv block: 96 months = 8 years
    Training ./submissions/whole_fields ...
    /Users/kegl/anaconda/lib/python2.7/site-packages/sklearn/linear_model/coordinate_descent.py:484: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Fitting data with very small alpha may cause precision problems.
      ConvergenceWarning)
    CV fold 0
    	train rmse = 0.441
    	valid rmse = 0.59
    	test rmse = 0.63
    CV fold 1
    	train rmse = 0.429
    	valid rmse = 0.541
    	test rmse = 0.587
    CV fold 2
    	train rmse = 0.416
    	valid rmse = 0.512
    	test rmse = 0.563
    CV fold 3
    	train rmse = 0.407
    	valid rmse = 0.509
    	test rmse = 0.551
    CV fold 4
    	train rmse = 0.404
    	valid rmse = 0.529
    	test rmse = 0.558
    CV fold 5
    	train rmse = 0.396
    	valid rmse = 0.532
    	test rmse = 0.538
    CV fold 6
    	train rmse = 0.407
    	valid rmse = 0.501
    	test rmse = 0.532
    CV fold 7
    	train rmse = 0.391
    	valid rmse = 0.466
    	test rmse = 0.515
    ----------------------------
    train rmse = 0.411 ± 0.0158
    valid rmse = 0.522 ± 0.0335
    test rmse = 0.559 ± 0.0336
    ----------------------------
    Testing if the notebook can be converted to html
    [NbConvertApp] Converting notebook ./sea_ice_starting_kit.ipynb to html
    [NbConvertApp] Writing 866712 bytes to ./sea_ice_starting_kit.html

This one computes the vector of input variables at times `[t, t-1, ... t-window_size+1]` then concatenate. Spatial variables are averaged along lat and lon coordinates. We use this with a random forest regressor.

```python
%%file submissions/mean_ten/ts_feature_extractor.py
import numpy as np

class FeatureExtractor(object):

    def __init__(self):
        self.window_size = 10

    def transform(self, X_ds):
        """Compute the vector of input variables in window of a given size.

        Compute the vector of input variables at times
        [t, t-1, ... t-window_size+1] then concatenate. Spatial variables
        will be averaged along lat and lon coordinates.
        """
        # This is the range for which features should be provided. Strip
        # the burn-in from the beginning and the prediction look-ahead from
        # the end.
        valid_range = np.arange(X_ds.attrs['n_burn_in'], len(X_ds['time']))
        # We convert the Dataset into a 4D DataArray
        X_xr = X_ds.to_array()
        # We compute the mean over the lat and lon axes
        mean_xr = np.mean(X_xr, axis=(2, 3))
        mean_array_transposed = mean_xr.values.T
        # We concatenate the past window_size means
        mean_array_c = np.concatenate(
            [np.roll(mean_array_transposed, i)
             for i in range(self.window_size)], axis=1)
        # We slice the valid range
        X_array = mean_array_c[valid_range]
        return X_array

```

    Overwriting submissions/mean_ten/ts_feature_extractor.py

```python
%%file submissions/mean_ten/regressor.py
from sklearn.base import BaseEstimator
from sklearn.ensemble import RandomForestRegressor

class Regressor(BaseEstimator):
    def __init__(self):
        self.reg = RandomForestRegressor(n_estimators=10, max_leaf_nodes=500)

    def fit(self, X, y):
        self.reg.fit(X, y)

    def predict(self, X):
        return self.reg.predict(X)

```

    Overwriting submissions/mean_ten/regressor.py

```python
!ramp_test_submission --submission mean_ten
```

    Testing Arctic sea ice forecast
    Reading train and test files from ./data ...
    Reading cv ...
    length of common block: 912 months = 76 years
    length of validation block: 768 months = 64 years
    length of each cv block: 96 months = 8 years
    Training ./submissions/mean_ten ...
    CV fold 0
    	train rmse = 0.459
    	valid rmse = 0.606
    	test rmse = 0.713
    CV fold 1
    	train rmse = 0.454
    	valid rmse = 0.594
    	test rmse = 0.673
    CV fold 2
    	train rmse = 0.46
    	valid rmse = 0.525
    	test rmse = 0.683
    CV fold 3
    	train rmse = 0.415
    	valid rmse = 0.548
    	test rmse = 0.586
    CV fold 4
    	train rmse = 0.385
    	valid rmse = 0.512
    	test rmse = 0.609
    CV fold 5
    	train rmse = 0.382
    	valid rmse = 0.565
    	test rmse = 0.622
    CV fold 6
    	train rmse = 0.379
    	valid rmse = 0.527
    	test rmse = 0.602
    CV fold 7
    	train rmse = 0.358
    	valid rmse = 0.547
    	test rmse = 0.601
    ----------------------------
    train rmse = 0.411 ± 0.0386
    valid rmse = 0.553 ± 0.0312
    test rmse = 0.636 ± 0.0438
    ----------------------------
    Testing if the notebook can be converted to html
    [NbConvertApp] Converting notebook ./sea_ice_starting_kit.ipynb to html
    [NbConvertApp] Writing 867447 bytes to ./sea_ice_starting_kit.html

```python
%%file submissions/monthly_means/ts_feature_extractor.py
import numpy as np

class FeatureExtractor(object):

    def __init__(self):
        pass

    def transform(self, X_ds):
        """Compute the monthly averages of the ice_area.

        Corresponding to the month to predict.
        The code could be simplified but in this way it is general, can be
        used for the other variables as well.
        """
        # This is the range for which features should be provided. Strip
        # the burn-in from the beginning and the prediction look-ahead from
        # the end.
        valid_range = np.arange(X_ds.attrs['n_burn_in'], len(X_ds['time']))
        # We convert the Dataset into a 4D DataArray
        X_xr = X_ds.to_array()
        # We compute the mean over the lat and lon axes
        mean_array = np.mean(X_xr, axis=(2, 3)).values
        # We group the 8 monthly series into 8 x 12 monthly groups of series
        monthly_groups = mean_array.reshape(
            (mean_array.shape[0], 12, -1), order='F')
        # We compute cumulative means in each group
        monthly_means = np.cumsum(monthly_groups, axis=2)\
            / (1. + np.arange(monthly_groups.shape[2]))
        # We repeat each mean 12 times
        monthly_means_per_month = np.repeat(monthly_means, 12, axis=2)
        # We pad m 0s to the series corresponding to month m, no single-line
        # operation for this
        for j in range(monthly_means_per_month.shape[0]):
            for m in range(12):
                monthly_means_per_month[j, m] = np.roll(
                    monthly_means_per_month[j, m], m)
                monthly_means_per_month[j, m, :m] = 0
        # We reshape and transpose it into one vector per month
        monthly_ice_area_mean = monthly_means_per_month[0]
        # At each month t we use the running mean correponting to month t - 8
        X_array = np.array(
            [monthly_ice_area_mean[(t + X_ds.n_lookahead - 12) % 12][t]
             for t in range(monthly_ice_area_mean.shape[1])])
        # We slice the valid range
        X_array = X_array[valid_range].reshape(-1, 1)
        return X_array

```

    Overwriting submissions/monthly_means/ts_feature_extractor.py

```python
!ramp_test_submission --submission monthly_means
```

    Testing Arctic sea ice forecast
    Reading train and test files from ./data ...
    Reading cv ...
    length of common block: 912 months = 76 years
    length of validation block: 768 months = 64 years
    length of each cv block: 96 months = 8 years
    Training ./submissions/monthly_means ...
    CV fold 0
    	train rmse = 0.332
    	valid rmse = 0.342
    	test rmse = 0.37
    CV fold 1
    	train rmse = 0.336
    	valid rmse = 0.334
    	test rmse = 0.368
    CV fold 2
    	train rmse = 0.342
    	valid rmse = 0.319
    	test rmse = 0.369
    CV fold 3
    	train rmse = 0.341
    	valid rmse = 0.318
    	test rmse = 0.367
    CV fold 4
    	train rmse = 0.336
    	valid rmse = 0.329
    	test rmse = 0.368
    CV fold 5
    	train rmse = 0.333
    	valid rmse = 0.342
    	test rmse = 0.367
    CV fold 6
    	train rmse = 0.339
    	valid rmse = 0.299
    	test rmse = 0.366
    CV fold 7
    	train rmse = 0.337
    	valid rmse = 0.284
    	test rmse = 0.367
    ----------------------------
    train rmse = 0.337 ± 0.0034
    valid rmse = 0.321 ± 0.0192
    test rmse = 0.368 ± 0.0012
    ----------------------------
    Testing if the notebook can be converted to html
    [NbConvertApp] Converting notebook ./sea_ice_starting_kit.ipynb to html
    [NbConvertApp] Writing 862869 bytes to ./sea_ice_starting_kit.html

The following feature extractor "cheats": it rolls the feature array backwards, making the future part of the features. Check what happens when the unit test is run.

```python
%%file submissions/illegal_lookahead/ts_feature_extractor.py
import numpy as np

class FeatureExtractor(object):

    def __init__(self):
        pass

    def transform(self, X_ds):
        """Compute the vector of input variables at time t. Spatial variables will
        be averaged along lat and lon coordinates."""
        # This is the range for which features should be provided. Strip
        # the burn-in from the beginning and the prediction look-ahead from
        # the end.
        valid_range = np.arange(X_ds.attrs['n_burn_in'], len(X_ds['time']))
        # We convert the Dataset into a 4D DataArray
        X_xr = X_ds.to_array()
        # We compute the mean over the lat and lon axes
        mean_xr = np.mean(X_xr, axis=(2, 3))
        # We convert it into numpy array, transpose, and slice the valid range
        # We roll it backwards to see what happens when the feature extractor
        # attempts to look into the future.
        X_array = mean_xr.values.T[np.roll(valid_range, -2)]
        return X_array

```

    Overwriting submissions/illegal_lookahead/ts_feature_extractor.py

```python
!ramp_test_submission --submission illegal_lookahead
```

    Testing Arctic sea ice forecast
    Reading train and test files from ./data ...
    Reading cv ...
    length of common block: 912 months = 76 years
    length of validation block: 768 months = 64 years
    length of each cv block: 96 months = 8 years
    Training ./submissions/illegal_lookahead ...
    Traceback (most recent call last):
      File "/Users/kegl/anaconda/bin/ramp_test_submission", line 11, in <module>
        load_entry_point('ramp-workflow', 'console_scripts', 'ramp_test_submission')()
      File "/Users/kegl/Research/RAMP/ramp-workflow/rampwf/utils/command_line.py", line 51, in ramp_test_submission
        submission=sub)
      File "/Users/kegl/Research/RAMP/ramp-workflow/rampwf/utils/testing.py", line 79, in assert_submission
        module_path, X_train, y_train, train_is=train_is)
      File "/Users/kegl/Research/RAMP/ramp-workflow/rampwf/workflows/el_nino.py", line 59, in train_submission
        ts_fe, X_train_ds)
      File "/Users/kegl/Research/RAMP/ramp-workflow/rampwf/workflows/ts_feature_extractor.py", line 125, in test_submission
        raise AssertionError(message)
    AssertionError: The feature extractor looks into the future by at least 2 time steps

## Working in the notebook

When you are developing and debugging your submission, you may want to stay in the notebook and execute the workflow step by step. You can import `problem.py` and call the ingredients directly, or even deconstruct the code from [ramp-workflow](https://github.com/paris-saclay-cds/ramp-workflow).

```python
import imp
problem = imp.load_source('', 'problem.py')
```

Get the training data.

```python
X_train, y_train = problem.get_train_data()
```

Get the first cv fold, creating training and validation indices.

```python
train_is, test_is = list(problem.get_cv(X_train, y_train))[0]
```

    length of common block: 708 months = 59 years
    length of validation block: 672 months = 56 years
    length of each cv block: 84 months = 7 years

Train your starting kit.

```python
ts_fe, reg = problem.workflow.train_submission(
    'submissions/starting_kit', X_train, y_train, train_is)
```

Get the full prediction (train and validation).

```python
y_pred = problem.workflow.test_submission((ts_fe, reg), X_train)
```

Print the training and validation scores.

```python
score_function = problem.score_types[0]
```

```python
score_train = score_function(y_train[train_is], y_pred[train_is])
print(score_train)
```

    0.914748857316

```python
score_valid = score_function(y_train[test_is], y_pred[test_is])
print(score_valid)
```

    0.922932338866

Get the independent test data.

```python
X_test, y_test = problem.get_test_data()
```

Test the submission on it.

```python
y_test_pred = problem.workflow.test_submission((ts_fe, reg), X_test)
```

Print the test score.

```python
score_test = score_function(y_test, y_test_pred)
print(score_test)
```

    0.970616143599

If you want to execute training step by step, go to the [`el_nino`](https://github.com/paris-saclay-cds/ramp-workflow/blob/master/rampwf/workflows/el_nino.py), [`ts_feature_extractor`](https://github.com/paris-saclay-cds/ramp-workflow/blob/master/rampwf/workflows/ts_feature_extractor.py), and [`regressor`](https://github.com/paris-saclay-cds/ramp-workflow/blob/master/rampwf/workflows/regressor.py) workflows and deconstruct them.

```python
ts_feature_extractor = imp.load_source(
    '', 'submissions/starting_kit/ts_feature_extractor.py')
ts_fe = ts_feature_extractor.FeatureExtractor()
regressor = imp.load_source(
    '', 'submissions/starting_kit/regressor.py')
reg = regressor.Regressor()
```

```python
n_burn_in = X_ds.n_burn_in
# X_ds contains burn-in so it needs to be extended by n_burn_in
# timesteps. This assumes that train_is is a block of consecutive
# time points.
burn_in_range = np.arange(train_is[-1], train_is[-1] + n_burn_in)
extended_train_is = np.concatenate((train_is, burn_in_range))
X_train_ds = X_ds.isel(time=extended_train_is)
X_train_array = ts_fe.transform(X_train_ds)
print(X_train_array)
reg.fit(X_train_array[train_is], y_train[train_is])
```

    [[  1.48342934e+01   2.75403087e+02  -9.99870661e-03 ...,   1.00945582e+05
        1.04329444e+01   4.36368580e-01]
     [  1.57851725e+01   2.74862356e+02  -4.93761828e-03 ...,   1.01028900e+05
        1.00348801e+01   4.46457947e-01]
     [  1.58088322e+01   2.74467664e+02  -6.26856377e-03 ...,   1.00970414e+05
        1.02867818e+01   4.46326237e-01]
     ...,
     [  7.11175919e+00   2.77646321e+02  -1.24813862e-02 ...,   1.00744758e+05
        1.12947455e+01   4.81643986e-01]
     [  8.72921658e+00   2.76421950e+02  -1.22162999e-02 ...,   1.00820864e+05
        1.00709387e+01   4.62528035e-01]
     [  1.07231407e+01   2.75346836e+02  -1.20509273e-02 ...,   1.00832401e+05
        1.00267506e+01   4.40711749e-01]]

```python
X_train_array = ts_fe.transform(X_train)
y_pred = reg.predict(X_train_array)
```

```python
X_test_array = ts_fe.transform(X_test)
y_test_pred = reg.predict(X_test_array)
```

## More information

You can find more information in the [README](https://github.com/paris-saclay-cds/ramp-workflow/blob/master/README.md) of the [ramp-workflow library](https://github.com/paris-saclay-cds/ramp-workflow).

## Contact

Don't hesitate to [contact us](mailto:admin@ramp.studio?subject=sea ice notebook).


# PyTorch Assignment: Reinforcement Learning (RL)

**[Duke Community Standard](http://integrity.duke.edu/standard.html): By typing your name below, you are certifying that you have adhered to the Duke Community Standard in completing this assignment.**

Name:

### Short answer

1\. One of the fundamental challenges of reinforcement learning is balancing *exploration* versus *exploitation*. What do these two terms mean, and why do they present a challenge?

`[Your answer here]`

2\. Another fundamental reinforcement learning challenge is what is known as the *credit assignment problem*, especially when rewards are sparse. What do we mean by the phrase, and why does it make learning especially difficult?

`[Your answer here]`

### Deep SARSA Cart Pole

[SARSA (state-action-reward-state-action)](https://en.wikipedia.org/wiki/State–action–reward–state–action) is another Q value algorithm that resembles Q-learning quite closely:

Q-learning update rule:
\begin{equation}
Q_\pi (s_t, a_t) \leftarrow (1 - \alpha) \cdot Q_\pi(s_t, a_t) + \alpha \cdot \big(r_t + \gamma \max_a Q_\pi(s_{t+1}, a)\big)
\end{equation}

SARSA update rule:
\begin{equation}
Q_\pi (s_t, a_t) \leftarrow (1 - \alpha) \cdot Q_\pi(s_t, a_t) + \alpha \cdot \big(r_t + \gamma Q_\pi(s_{t+1}, a_{t+1})\big)
\end{equation}

Unlike Q-learning, which is considered an *off-policy* network, SARSA is an *on-policy* algorithm.
When Q-learning calculates the estimated future reward, it must "guess" the future, starting with the next action the agent will take. In Q-learning, we assume the agent will take the best possible action: $\max_a Q_\pi(s_{t+1}, a)$. SARSA, on the other hand, uses the action that was actually taken next in the episode we are learning from: $Q_\pi(s_{t+1}, a_{t+1})$. In other words, SARSA learns from the next action he actually took (on policy), as opposed to what the max possible Q value for the next state was (off policy).

Build an RL agent that uses SARSA to solve the Cart Pole problem.

*Hint: You can and should reuse the Q-Learning agent we went over earlier. In fact, if you know what you're doing, it's possible to finish this assignment in about 30 seconds.*

```python
### YOUR CODE HERE ###

```


```
# @title Copyright 2022 The Cirq Developers
# 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
#
# https://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.
```

# Classical control

    View on QuantumAI

    Run in Google Colab

    View source on GitHub

    Download notebook

```
try:
    import cirq
except ImportError:
    print("installing cirq...")
    !pip install --quiet cirq
    import cirq

    print("installed cirq.")
```

While some quantum algorithms can be defined entirely at the quantum level, there are many others (notably including [teleportation](/cirq/tutorials/educators/textbook_algorithms#quantum_teleportation) and [error correction](https://www.nature.com/articles/s41586-021-03588-y)) which rely on classical measurement results from one part of the algorithm to control operations in a later section.

To represent this, Cirq provides the `ClassicallyControlledOperation`. Following the pattern of controlled operations, a classically-controlled version of any `Operation` can be constructed by calling its `with_classical_controls` method with the control condition(s).

## Basic conditions

In the example below, `H` will only be applied to `q1` if the previous measurement "a" returns a 1. More generally, providing some string `"cond"` to `with_classical_controls` creates a `cirq.ClassicallyControlledOperation` with a `cirq.KeyCondition` whose key is `"cond"`. A `KeyCondition` will only trigger, and apply the operation it controls, if a preceding measurement with the same key measured **one or more** qubits in the $|1\rangle$ state.

```
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit(
    cirq.H(q0),
    cirq.measure(q0, key='a'),
    cirq.H(q1).with_classical_controls('a'),
    cirq.measure(q1, key='b'),
)
print(circuit)
print(cirq.Simulator().run(circuit, repetitions=1000).histogram(key='b'))
```

The results from running the circuit on the simulator match expectation. `H` applied to qubit `q0` means that qubit will be $|1\rangle$ half of the time on average. When `H` is then applied to qubit `q1`, (half of the time), `q1` will measure $|1\rangle$ a quarter of the time and $|0\rangle$ three-quarters of the time.

Using just these conditions, we can construct the [quantum teleportation](/cirq/tutorials/educators/textbook_algorithms#quantum_teleportation) circuit:

```
# Teleports `_message` from Alice to Bob.
alice = cirq.NamedQubit('alice')
bob = cirq.NamedQubit('bob')
message = cirq.NamedQubit('_message')

message_circuit = cirq.Circuit(
    # Create the message.
    cirq.X(message) ** 0.371,
    cirq.Y(message) ** 0.882,
)

teleport_circuit = cirq.Circuit(
    # Create Bell state to be shared between Alice and Bob.
    cirq.H(alice),
    cirq.CNOT(alice, bob),
    # Prepare message circuit
    message_circuit,
    # Bell measurement of the message and Alice's entangled qubit.
    cirq.CNOT(message, alice),
    cirq.H(message),
    cirq.measure(message, key='M'),
    cirq.measure(alice, key='A'),
    # Uses the two classical bits from the Bell measurement to recover the
    # original quantum message on Bob's entangled qubit.
    cirq.X(bob).with_classical_controls('A'),
    cirq.Z(bob).with_classical_controls('M'),
)
print(circuit)

# Simulate the message and teleport circuits for Bloch vectors to compare
#     the state of the teleported qubit before and after teleportation.
sim = cirq.Simulator()
message_bloch_vector = cirq.bloch_vector_from_state_vector(
    sim.simulate(message_circuit).final_state_vector, index=0
)
teleport_bloch_vector = cirq.bloch_vector_from_state_vector(
    sim.simulate(teleport_circuit).final_state_vector, index=2
)
print(f"Message Qubit State: {message_bloch_vector}")
print(f"Teleported Bob's Qubit state: {teleport_bloch_vector}")
```

This example separately simulated the message qubit after its construction, and Bob's qubit after teleportation of the message. The fact that the Bloch vectors of each respective qubit are the same indicate that the circuit successfully teleported the message qubit's state onto Bob's qubit.

## Sympy conditions

Cirq also supports more complex control conditions: providing some `sympy` expression `"expr"` to `with_classical_controls` creates a `ClassicallyControlledOperation` with a `SympyCondition`. That condition will only trigger if `"expr"` evaluates to a "truthy" value (`bool(expr) == True`), and uses measurement results to resolve any variables in the expression.

In this example, `X` will only be applied to `q2` if `a == b`; in other words, $|q_0q_1\rangle$ must be either $|00\rangle$ or $|11\rangle$. This is verifiable with the simulated result data, where the `c` measurement key for qubit `q2` is always `1` when `a` and `b` are `00` or `11`, and `0` otherwise.

```
import sympy

q0, q1, q2 = cirq.LineQubit.range(3)
a, b = sympy.symbols('a b')
sympy_cond = sympy.Eq(a, b)
circuit = cirq.Circuit(
    cirq.H.on_each(q0, q1),
    cirq.measure(q0, key='a'),
    cirq.measure(q1, key='b'),
    cirq.X(q2).with_classical_controls(sympy_cond),
    cirq.measure(q2, key='c'),
)
print(circuit)
results = cirq.Simulator(seed=2).run(circuit, repetitions=8)
print(results.data)
```

## Combining conditions

Multiple conditions of either type can be specified to `with_classical_controls`, in which case the resulting `ClassicallyControlledOperation` will only trigger if _all_ conditions trigger. Similarly, calling `with_classical_controls` on an existing `ClassicallyControlledOperation` will require all new and pre-existing conditions to trigger for the operation to trigger.

```
q0, q1, q2, q3, q4 = cirq.LineQubit.range(5)
a = sympy.symbols('a')
sympy_cond = sympy.Eq(a, 0)
circuit = cirq.Circuit(
    cirq.H.on_each(q0, q1, q2),
    cirq.measure(q0, q1, key='a'),
    cirq.measure(q2, key='b'),
    cirq.X(q3).with_classical_controls('b', sympy_cond),
    cirq.X(q4).with_classical_controls('b').with_classical_controls(sympy_cond),
    cirq.measure(q3, key='c'),
    cirq.measure(q4, key='d'),
)
print(circuit)
results = cirq.Simulator(seed=1).run(circuit, repetitions=8)
print(results.data)
```

First, remember that the value of a measurement key for multiple qubits will be an integer representative of the bit string of those qubits' measurements. You can see this in the data for `a`, the measurement key for both `q0` and `q1`, which has values in the range `[0, 3]`. The sympy condition `Eq(a, 0)` will then only trigger when both of those qubits individually measure `0`.

This means that `X(q3).with_classical_controls('b', sympy_cond)` only triggers when `b`'s qubit `q2` measures `1` and `a = 0` is true (`q0` and `q1` measure `0`). This is consistent with the simulated results, for both `c` (`q3`'s key) and `d` (`q4`'s key).

Finally, the fact that `c` and `d` are always identical serves as a reminder that chaining multiple calls of `with_classical_controls()` together is equivalent to calling it once with multiple arguments.

## Variable scope

When used with `cirq.CircuitOperation`, classically controlled operations will be resolved using local repetition IDs, if any. This is the only way to create a non-global variable scope within a circuit. A simple example of this is shown below, where the controls inside and outside a subcircuit rely on measurements in their respective scopes:

```
q0 = cirq.LineQubit(0)
subcircuit = cirq.FrozenCircuit(cirq.measure(q0, key='a'), cirq.X(q0).with_classical_controls('a'))
circuit = cirq.Circuit(
    cirq.measure(q0, key='a'),
    cirq.CircuitOperation(subcircuit, repetitions=2),
    cirq.X(q0).with_classical_controls('a'),
)
print("Original Circuit")
print(circuit)
print("Circuit with nested circuit unrolled.")
print(cirq.CircuitOperation(cirq.FrozenCircuit(circuit)).mapped_circuit(deep=True))
```

The measurement key `a` is present both in the outer circuit and the `FrozenCircuit` nested within it, but these two keys are different due to their different scopes. After unrolling the inner circuit twice, these inner `a`s get prefixed by the repetition number and becomes new, separate measurement keys, `0:a` and `1:a`, that don't interact with each other or the original `a`.

More complex scoping behavior is described in the [classically controlled operation tests](https://github.com/quantumlib/Cirq/blob/master/cirq-core/cirq/ops/classically_controlled_operation_test.py).

## Using with transformers

Cirq [transformers](transformers.ipynb) are aware of classical control and will avoid changes which move a control before its corresponding measurement. Additionally, for some simple cases the [`defer_measurements` transformer](https://github.com/quantumlib/Cirq/blob/6e0e164e8ac1c2f28a1f3389370fffb50a4d2a4f/cirq-core/cirq/transformers/measurement_transformers.py#L58) can convert a classically-controlled circuit into a purely-quantum circuit:

```
q0 = cirq.LineQubit(0)
circuit = cirq.Circuit(
    cirq.measure(q0, key='a'), cirq.X(q1).with_classical_controls('a'), cirq.measure(q1, key='b')
)
deferred = cirq.defer_measurements(circuit)
print("Original circuit:")
print(circuit)
print("Measurement deferred:")
print(deferred)
```

## Compatibility

The Cirq built-in simulators provide support for classical control, but caution should be exercised when exporting these circuits to other environments. `ClassicallyControlledOperation` is fundamentally different from other operations in that it requires access to the measurement results, and simulators or hardware that do not explicitly support this will not be able to run `ClassicallyControlledOperation`s.