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

# <h1>Einführung in NumpPy Teil 2<span class="tocSkip"></span></h1>
# <div class="toc"><ul class="toc-item"><li><span><a href="#Broadcasting" data-toc-modified-id="Broadcasting-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>Broadcasting</a></span></li><li><span><a href="#Beispiel-zur-Verwendung-von-Index-Arrays." data-toc-modified-id="Beispiel-zur-Verwendung-von-Index-Arrays.-2"><span class="toc-item-num">2&nbsp;&nbsp;</span>Beispiel zur Verwendung von Index-Arrays.</a></span></li><li><span><a href="#Verwende-boolsche-Ausdrücke-bei-der-Definition-von-Matrizen" data-toc-modified-id="Verwende-boolsche-Ausdrücke-bei-der-Definition-von-Matrizen-3"><span class="toc-item-num">3&nbsp;&nbsp;</span>Verwende boolsche Ausdrücke bei der Definition von Matrizen</a></span></li><li><span><a href="#Achtung" data-toc-modified-id="Achtung-4"><span class="toc-item-num">4&nbsp;&nbsp;</span>Achtung</a></span></li><li><span><a href="#Universal-functions-(ufunc)" data-toc-modified-id="Universal-functions-(ufunc)-5"><span class="toc-item-num">5&nbsp;&nbsp;</span>Universal functions (ufunc)</a></span></li></ul></div>

# # Einführung in NumPy
# ## Broadcasting
# +, -, * , %, / &ast;&ast; wirken eintragsweise, __wobei die _singleton_ Dimension expandiert wird__.
# 
# _singleton_ : fehlende Dimenson oder Dimension der Länge 1.

# In[2]:


import numpy as np


# In[3]:


A = np.array([[1, 2], [3, 4]])
v = np.array([-5, 7])
display(A, v)


# In[4]:


# Das erste Element von v wird zu jeder zu jedem Element der ersten Spalte addiert
A+v


# In[5]:


#' Das erste Element von v wird ...
A*v


# In[6]:


A**v.astype('float')


# In[7]:


# zu jedem Eintrag wird 1 addiert
A + 1


# In[8]:


# jeder Eintrag wird durch 3 dividiert
A / 3


# In[9]:


# 3 wird durch jeden Eintrag dividiert
3 / A


# __Broadcastingregel__
# (aus https://numpy.org/doc/stable/user/basics.broadcasting.html)
# 
# When operating on two arrays, NumPy compares their shapes element-wise. It starts with the trailing (i.e. rightmost) dimensions and works its way left. Two dimensions are compatible when
# 
# 1. they are equal, or
# 
# 2. one of them is 1
# 
# If these conditions are not met, a ValueError: operands could not be broadcast together exception is thrown, indicating that the arrays have incompatible shapes. The size of the resulting array is the size that is not 1 along each axis of the inputs.
# 
# Arrays do not need to have the same number of dimensions. 

# In[10]:


DreiArray = np.array(np.arange(3*4*5)).reshape(3, 4, 5)
DreiArray.shape


# In[11]:


ZweiArray = np.array(np.arange(3*4)).reshape(3, 4)


# In[12]:


DreiArray + ZweiArray


# In[13]:


DreiArray_neu = np.array(np.arange(3*4*5)).reshape(5, 3, 4)
DreiArray_neu.shape


# In[14]:


EinsArray = np.array(np.arange(5)).reshape(5, 1, 1)


# In[15]:


#DreiArray_neu + ZweiArray + EinsArray


# In[34]:


EinsArray


# In[36]:


np.squeeze(EinsArray)


# ## Beispiel zur Verwendung von Index-Arrays.
# Erzeuge einen Vektor, der die Gegendiagonale einer Matrix enthält

# In[16]:


A = np.arange(16).reshape(4, 4)
A


# In[17]:


i = np.arange(4)
j = i[::-1]
j, i


# In[18]:


b = A[i, j]
b


# ## Verwende boolsche Ausdrücke bei der Definition von Matrizen

# In[19]:


# Bsp: Setze alle negativen Matrixeinträge auf Null
A 
B = A - 2*A.T 
B


# In[20]:


B[B < 0] = 0
B


# In[21]:


# elementweises logisches "oder" und "und"
B = A - A.T
B


# In[22]:


# Welche Elemente von B sind kleiner als -5 ODER größer als 6?
index_array = np.logical_or(B < -5, B > 6)
index_array


# In[23]:


# Welche Elemente von B sind kleiner als 2 UND größer als 0?
np.logical_and(B < 7, B > 0)


# In[24]:


B[index_array] = 100
B


# ## Achtung
# 
# NumPy macht kein Ducktyping.
# 
# ... duck typing is an application of the duck test—"If it walks like a duck and it quacks like a duck, then it must be a duck" https://en.wikipedia.org/wiki/Duck_typing

# In[25]:


a = np.array([1, -2.25])


# In[26]:


a.dtype


# In[27]:


a[0] = 1.+1j


# In[28]:


ac = np.array([1, -2.25], dtype='complex')
ac.dtype


# In[29]:


ac[0] = 10.+10j


# In[30]:


a + ac


# In[91]:


np.sqrt(a)


# In[32]:


np.sqrt(a.astype('complex'))


# Die NumPy-Funktionen in emath (power, exp, log, und inverse trigonometrische Funktionen) passen den Ausgabedatentyp an, wenn der Ausgabedatentyp vom Eingabedatentyp abweicht.

# In[93]:


np.emath.sqrt(a)


# In[95]:


np.info(np.emath)


# ## Universal functions (ufunc)
# 
# Die universellen Funktionen aus NumPy (sin, cos, tan, arcsin, arccos, sinh, cosh, arcsinh, ... exp, log, log2, log10, sqrt, ...) wirken elementweise, wenn sie auf NumPy Arrays angewandt werden. https://numpy.org/doc/stable/reference/ufuncs.html

# In[2]:


x = np.linspace(0, 5*np.pi, 10) # 10 äquidistante Zahlen zwischen 0 und 5 pi
x, np.sin(x)


# In[33]:


np.info(np.expm1)


# In[4]:


get_ipython().run_cell_magic('timeit', '', 'y = np.sin(x)\n')


# In[6]:


get_ipython().run_cell_magic('timeit', '', 'l = []\nfor xx in x:\n    l.append(np.sin(xx))\ny = np.array(l)\n')

