mylist = list(range(10))
print(mylist)

"""
Explanation: EuroSciPy 2018: NumPy tutorial
Let's do some slicing
End of explanation
"""


matrix = [[0, 1, 2],
          [3, 4, 5],
          [6, 7, 8]]

"""
Explanation: Use slicing to produce the following outputs:
[2, 3, 4, 5]
[0, 1, 2, 3, 4]
[6, 7, 8, 9]
[0, 2, 4, 6, 8]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[7, 5, 3]
Matrices and lists of lists
End of explanation
"""


np.lookfor('create array')

help(np.array)

"""
Explanation: Get the second row by slicing twice
Try to get the second column by slicing. Do not use a list comprehension!
Getting started
Import the NumPy package
Create an array
End of explanation
"""


myarray1 = np.arange(6)
myarray1

def array_attributes(a):
    for attr in ('ndim', 'size', 'itemsize', 'dtype', 'shape', 'strides'):
        print('{:8s}: {}'.format(attr, getattr(a, attr)))

array_attributes(myarray1)

"""
Explanation: The variable matrix contains a list of lists. Turn it into an ndarray and assign it to the variable myarray. Verify that its type is correct.
For practicing purposes, arrays can conveniently be created with the arange method.
End of explanation
"""


np.arange(1, 160, 10, dtype=np.int8)

"""
Explanation: Data types
Use np.array() to create arrays containing
 * floats
 * complex numbers
 * booleans
 * strings
and check the dtype attribute.
Do you understand what is happening in the following statement?
End of explanation
"""


myarray2 = myarray1.reshape(2, 3)
myarray2

array_attributes(myarray2)

myarray3 = myarray1.reshape(3, 2)

array_attributes(myarray3)

"""
Explanation: Strides
End of explanation
"""


a = np.arange(9).reshape(3, 3)
a

a.T

"""
Explanation: Views
Set the first entry of myarray1 to a new value, e.g. 42.
What happened to myarray2?
What happens when a matrix is transposed?
End of explanation
"""


a.strides

a.T.strides

"""
Explanation: Check the strides!
End of explanation
"""


a = np.arange(4)
b = a
id(a), id(b)

"""
Explanation: View versus copy
identical object
End of explanation
"""


b = a[:]
id(a), id(b)

a[0] = 42
a, b

"""
Explanation: view: a different object working on the same data
End of explanation
"""


a = np.arange(4)
b = np.copy(a)
id(a), id(b)

a[0] = 42
a, b

"""
Explanation: an independent copy
End of explanation
"""


np.arange(5, 30, 5)

"""
Explanation: Some array creation routines
numerical ranges
arange(start, stop, step), stop is not included in the array
End of explanation
"""


np.logspace(-2, 2, 5)

np.logspace(0, 4, 9, base=2)

"""
Explanation: arange resembles range, but also works for floats
Create the array [1, 1.1, 1.2, 1.3, 1.4, 1.5]
linspace(start, stop, num) determines the step to produce num equally spaced values, stop is included by default
Create the array [1., 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.]
For equally spaced values on a logarithmic scale, use logspace.
End of explanation
"""


import matplotlib.pyplot as plt

%matplotlib inline

x = np.linspace(0, 10, 100)
y = np.cos(x)

plt.plot(x, y)

"""
Explanation: Application
End of explanation
"""


np.zeros((4, 4))

"""
Explanation: Homogeneous data
End of explanation
"""


np.ones((2, 3, 3))

"""
Explanation: Create a 4x4 array with integer zeros
End of explanation
"""


np.diag([1, 2, 3, 4])

"""
Explanation: Create a 3x3 array filled with tens
Diagonal elements
End of explanation
"""


np.info(np.eye)

"""
Explanation: diag has an optional argument k. Try to find out what its effect is.
Replace the 1d array by a 2d array. What does diag do?
End of explanation
"""


np.random.rand(5, 2)

np.random.seed(1234)
np.random.rand(5, 2)

data = np.random.rand(20, 20)
plt.imshow(data, cmap=plt.cm.hot, interpolation='none')
plt.colorbar()

casts = np.random.randint(1, 7, (100, 3))
plt.hist(casts, np.linspace(0.5, 6.5, 7))

"""
Explanation: Create the 3x3 array
[[2, 1, 0],
 [1, 2, 1],
 [0, 1, 2]]
Random numbers
End of explanation
"""


a = np.arange(10)

"""
Explanation: Indexing and slicing
1d arrays
End of explanation
"""


a = np.arange(40).reshape(5, 8)

a %3 == 0

a[a %3 == 0]

"""
Explanation: Create the array [7, 8, 9]
Create the array [2, 4, 6, 8]
Create the array [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Higher dimensions
Fancy indexing ‒ Boolean mask
End of explanation
"""


nmax = 50
integers = np.arange(nmax)
is_prime = np.ones(nmax, dtype=bool)
is_prime[:2] = False
for j in range(2, int(np.sqrt(nmax))+1):
    if is_prime[j]:
        print(integers[is_prime])
        is_prime[j*j::j] = False
print(integers[is_prime])

"""
Explanation: Application: sieve of Eratosthenes
End of explanation
"""


a = np.arange(24).reshape(2, 3, 4)
a

"""
Explanation: Axes
Create an array and calculate the sum over all elements
Now calculate the sum along axis 0 ...
and now along axis 1
Identify the axis in the following array
End of explanation
"""


a = np.arange(4)
b = np.arange(4, 8)
a, b

a+b

a*b

"""
Explanation: Axes in more than two dimensions
Create a three-dimensional array
Produce a two-dimensional array by cutting along axis 0 ...
and axis 1 ...
and axis 2
What do you get by simply using the index [0]?
What do you get by using [..., 0]?
Exploring numerical operations
End of explanation
"""


length_of_walk = 10000
realizations = 5
angles = 2*np.pi*np.random.rand(length_of_walk, realizations)
x = np.cumsum(np.cos(angles), axis=0)
y = np.cumsum(np.sin(angles), axis=0)
plt.plot(x, y)
plt.axis('scaled')

plt.plot(np.hypot(x, y))

plt.plot(np.mean(x**2+y**2, axis=1))
plt.axis('scaled')

"""
Explanation: Operations are elementwise. Check this by multiplying two 2d array...
... and now do a real matrix multiplication
Application: Random walk
End of explanation
"""


%%timeit a = np.arange(1000000)
a**2

%%timeit xvals = range(1000000)
[xval**2 for xval in xvals]

%%timeit a = np.arange(100000)
np.sin(a)

import math

%%timeit xvals = range(100000)
[math.sin(xval) for xval in xvals]

"""
Explanation: Let's check the speed
End of explanation
"""


a = np.arange(12).reshape(3, 4)
a

a+1

a+np.arange(4)

a+np.arange(3)

np.arange(3)

np.arange(3).reshape(3, 1)

a+np.arange(3).reshape(3, 1)

%%timeit a = np.arange(10000).reshape(100, 100); b = np.ones((100, 100))
a+b

%%timeit a = np.arange(10000).reshape(100, 100)
a+1

"""
Explanation: Broadcasting
End of explanation
"""


a = np.arange(5)
b = a[:, np.newaxis]

"""
Explanation: Create a multiplication table for the numbers from 1 to 10 starting from two appropriately chosen 1d arrays.
As an alternative to reshape one can add additional axes with newaxes:
End of explanation
"""


x = np.linspace(-40, 40, 200)
y = x[:, np.newaxis]
z = np.sin(np.hypot(x-10, y))+np.sin(np.hypot(x+10, y))
plt.imshow(z, cmap='viridis')

x, y = np.mgrid[-10:10:0.1, -10:10:0.1]

x

y

plt.imshow(np.sin(x*y))

x, y = np.mgrid[-10:10:50j, -10:10:50j]

x

y

plt.imshow(np.arctan2(x, y))

"""
Explanation: Check the shapes.
Functions of two variables
End of explanation
"""


#
# put code here to create a Boolean array which contains True if a point
# belongs to the Mandelbrot set
#
# reasonable values: 50 iterations, threshold at 100, and a 300x300 grid
# but feel free to choose other values
#
plt.imshow(imdata, cmap='gray')

"""
Explanation: It is natural to use broadcasting. Check out what happens when you replace mgrid by ogrid.
Application: Mandelbrot set
End of explanation
"""


import numpy.linalg as LA

a = np.arange(4).reshape(2, 2)
eigenvalues, eigenvectors = LA.eig(a)
eigenvalues

eigenvectors

"""
Explanation: Application: π from random numbers
Create an array of random numbers and determine the fraction of points with distance from the origin smaller than one. Determine an approximation for π.
Linear Algebra in NumPy
End of explanation
"""


from numpy.polynomial import polynomial as P

"""
Explanation: Explore whether the eigenvectors are the rows or the columns.
Try out eigvals and other methods offered by linalg which your are interested in
Determine the eigenvalue larger than one appearing in the Fibonacci problem. Verify the result by calculating the ratio of successive Fibonacci numbers. Do you recognize the result?
Application: Brownian motion

Simulate several trajectories for a one-dimensional
Brownian motion
Hint: np.random.choice
Plot the mean distance from the origin as a function
of time
Plot the variance of the trajectories as a function of
time

Application: identify entry closest to ½
Create a 2d array containing random numbers and generate a vector containing for each row the entry closest to one-half.
Polynomials
End of explanation
"""


p1 = P.Polynomial([1, 2])

p1.degree()

p1.roots()

p4 = P.Polynomial([24, -50, 35, -10, 1])

p4.degree()

p4.roots()

p4.deriv()

p4.integ()

P.polydiv(p4.coef, p1.coef)

"""
Explanation: Powers increase from left to right (index corresponds to power)
End of explanation
"""


from scipy import misc
face = misc.face(gray=True)
face

plt.imshow(face, cmap=plt.cm.gray)

"""
Explanation: Application: polynomial fit
Application: image manipulation
End of explanation
"""


