<jupyter_start><jupyter_text>Universidad de Costa Rica Facultad de Ciencias Escuela de Química Sección de Fisicoquímica Qu-0560 Tópicos de Química Física: Herramientas Computacionales para Química Profesor <NAME> Asistente no oficial: <NAME>  Módulo IV. Computación Numérica con Numpy  Objetivos de Aprendizaje**** Aprender el manejo de vectores en 1, 2 y N-dimensiones usando Numpy* Utilizar Numpy para resolver problemas de estadística* Utilizar Numpy para resolver problemas de Cálculo y Algebra Lineal  ¿ Que es Numpy?Numpy es el paquete Numérico de Python _Numeric Python_ , que permite realizar cálculos de computación científica con Python de forma eficiente.¿Que nos permite hacer?* Realizar cálculos numéricos con Python usando vectores de N-dimensiones.* Realizar operaciones que son rapidamente vectorizables mediante n-arrays.* Resolver problemas de Algebra Lineal, Cálculo y Ecuaciones diferenciales con mucha capacidad.* Permite simplicar de forma eficiente cálculos en Python que de otro modo tendrían que ser programados.Para mas información, visita la página del [autor](http://www.numpy.org/)  Importando Numpy.Recuerda nuestra primer sesión!Para importar determinados paquete__import numpy as np ___ Importa el paquete en la siguiente celda<jupyter_code>import numpy as np<jupyter_output><empty_output><jupyter_text>Recuerda, le hablaste a tu compa Python de la siguiente forma... Python, se que Jupyter ya se encuentra instalado Numpy, por lo tanto quiero que lo importes en esta hoja de Jupyter, así mismo deseo importarlo como __np__ (lo bauticé así porque quize )  Trabajando con vectores o arrays.De tus amadas clases de Física I (sarcasmo), te recordarás que un vector es un objeto matemático que tiene magnitud y dirección.También que podes trabajar vectores en términos de sus componentes en i,j y k vectores unitarios. Por ejemplo, supongamos un vector __A__ en el espacio de coordenas cartesianas. Podemos escribir el vector __A__ de la siguiente forma. $$ \vec{A} = A_{x} \hat{i} + A_{y} \hat{j} +  A_{z} \hat{k}$$Donde $A_{x}$ ,  $A_{y}$ ,  $A_{z}$ representan las componentes, elementos o entradas del vector __A__ Y así mismo $\hat{i}$ , $\hat{j}$ , $\hat{z}$ son las direcciones de cada componente o vector unitarios Por notación, vos podes escribir el vector __A__ de la siguiente forma  $$ \vec{A} = ( A_{x}, A_{y}, A_{z}) $$Verdad que la expresión anterior te parece a algo conocido? Mira bien, tenemos un objeto __A__ que se conforma por tres elementos , cada uno separado por una coma y todo encerrado entre paréntesis... Te suena a algo?  Correcto, es como si se tratase de un Tuple o una Lista   Por ahí va la cosa!Volvamos a nuestro vector __A__, supongamos que queremos mas generalizadad!Rebautizemos a nuestras componentes y vectores unitarios,  * $A_{x}$ =  $A_{1}$ * $A_{y}$ =  $A_{2}$ * $A_{z}$ =  $A_{3}$ Y los vectores unitarios.. *  $\hat{i}$ = $\hat{e_{1}}$ *  $\hat{j}$ = $\hat{e_{2}}$ *  $\hat{k}$ = $\hat{e_{3}}$ Entonces nuestro vector general __A__ queda de la siguiente forma$$ \vec{A} = A_{1} \hat{e_{1}} + A_{2} \hat{e_{2}} +  A_{3} \hat{e_{3}}$$$$ \vec{A} = ( A_{1}, A_{2}, A_{3}) $$ Sí queremos generalizar para 4 componentes o elementos.... $$ \vec{A} = A_{1} \hat{e_{1}} + A_{2} \hat{e_{2}} +  A_{3} \hat{e_{3}} +   A_{4} \hat{e_{4}}$$$$ \vec{A} = ( A_{1}, A_{2}, A_{3} ,  A_{4}) $$Para N-componentes tenemos que $$ \vec{A} = A_{1} \hat{e_{1}} + A_{2} \hat{e_{2}} +  A_{3} \hat{e_{3}} +  A_{4} \hat{e_{4}} + ... + A_{N} \hat{e_{N}} $$ O en notación de suma $$ \vec{A} = \sum_{k=1}^N A_{N} \hat{e_{N}} $$O notación por componentes $$ \vec{A} = ( A_{1}, A_{2}, A_{3} ,  A_{4} , ... ,  A_{N} ) $$ En las celdas anteriores generalizamos la noción de vector o array. Este concepto es de gran importancia en matemática y ciencia en general Como ya mencionabamos, un array en términos de componentes tiene estructura de la primitiva __lista__ ... Por lo tanto sí, en general en Python y otros lenguajes de programación, utilizamos los arrays o vectores con la notación que aprendimos para usar listas   El problema de las listas en Python.  ¿Por que debemos definir un Array en Python , si ya existen las listas ?Supongamos que tenemos dos vectores __A__ y __B__ con las siguientes compoentes $$ \vec{A} = 3 \hat{e_{1}} + 5 \hat{e_{2}} +  8 \hat{e_{3}}$$$$ \vec{A} = ( 3, 5, 8) $$$$ \vec{B} = 1 \hat{e_{1}} + 2 \hat{e_{2}} +  9 \hat{e_{3}}$$$$ \vec{B} = ( 1, 2, 9) $$Y que ahora queremos definir un nuevo vector __C__ como __A + B__ De tus clases de física y cálculo ,te recordarás que dos vectores se suman componente por componente Es decir:$$ \vec{C} = \vec{A} + \vec{B} =  (3 + 1) \hat{e_{1}} + (5 + 2) \hat{e_{2}} +  (8 + 9) \hat{e_{3}}$$$$ \vec{C} =(4,7,17) $$ Hagamos lo anterior con listas.Define dos listas llamadas A y B.Con los componentes respectivos<jupyter_code># Define la lista A

# Define la lista B<jupyter_output><empty_output><jupyter_text>Ahora crea la lista C como la suma de A y BC = A + B<jupyter_code># Suma la lista A y B y guarda el resultado en la variable C
# Imprime la lista C<jupyter_output><empty_output><jupyter_text>El resultado como te dio? ¿Te dio el resultado esperado C = (4 , 7, 17) ? Correcto, simplemente concatenó ambas listas y ya, mas no sumó componente por componente como debería ser Lo anterior ejemplifica el problema con las __listas__, si bien son bastante útiles y mucho mas fáciles de utilizar respecto a otros lenguajes... Las listas no siguen el comportamiento de un _array_ o _vector_Tratando de solucionar este problema, se crea Numpy y los arrays en Python! (tomando como bases las listas y tuples)   Creando arrays en Python.Para crea un array en Python. Primero importa el paquete Numpy Sí lo importas como _np_ tenés que recordár usar el np Por ejemplo supongamos que querés crear un vector con componentes 2,3 y 4 Usarás la siguiente sintaxis * a = np.array([2,3,4])O primero podes definir una lista * A =  [2 3, 4]Luego la pones dentro del np.array. * a = np.array(A)<jupyter_code>a = np.array([2,3,4])<jupyter_output><empty_output><jupyter_text>Preguntemos a Python que es a<jupyter_code>#Imprime el array a en pantalla<jupyter_output><empty_output><jupyter_text>De la misma manera que aprendimos en listas, podemos acceder a cada elemento del array, usando la notación index.Por ejemplo si quiero acceder al primer elemento del array<jupyter_code>a[0]<jupyter_output><empty_output><jupyter_text>Accede al elemento tres del array<jupyter_code># Accede al elemente 3 del array<jupyter_output><empty_output><jupyter_text>Si querés el tipo de objeto que es el array.Podes usar la función type()<jupyter_code># Corre esta celda
type(a)<jupyter_output><empty_output><jupyter_text>Okey okey, es un numpy array...Pero si quiero saber el tipo de ELEMENTOS que contiene el array?Usamos el método .dtype<jupyter_code># Corre la siguiente celda
a.dtype
Sí queres saber la cantidad de elementos que contiene el array usas el atributo .size
# Corre la siguiente celda
a.size<jupyter_output><empty_output><jupyter_text>__HINT__ te diste cuenta que usamos los atributos __a.size__ y __a.dtype__ ? Tienen forma de métodos, pero si recuerdas los métodos llevaban paréntesis () al final. Cuando trabajas con numpy no hace falta usar los paréntesis al final. En general la notación es nombre del array.atributo Por ejemplo* a.size* a.dtype Sí queres conocer el número de dimensiones del array usamos el atributo .ndim<jupyter_code># Corre la siguiente celda
a.ndim<jupyter_output><empty_output><jupyter_text>Podes crear un array con números reales!Crea un array con los elementos [3.1 , 11.02 , 6.2 , 213.2 , 5.2] y guardalo en la variable b<jupyter_code># Crea el array b
# Accede al segundo elemento del array b<jupyter_output><empty_output><jupyter_text>Pregunta a Python el .dtype del array b<jupyter_code># Cual es el dtype del array b
Pregunta a Python el número de componentes de b con .size
# Cual es el número de compoenentes de b<jupyter_output><empty_output><jupyter_text>Index y Slicing en arrays<jupyter_code>Vamos a crear el array c con los elementos 4 , 5, 6 y 20
#Crea el array C
Accede a su primer elemento
# Acceder al primer elemento<jupyter_output><empty_output><jupyter_text>Supongamos que queremos cambiar su primer elemento a 8 , para eso emplearemos el siguiente código<jupyter_code>c[0] = 8<jupyter_output><empty_output><jupyter_text>Cambia el tercer elemento (6) al valor 11<jupyter_code>#Cambia el tercer elemento<jupyter_output><empty_output><jupyter_text>Para selecciones del segundo elemento al cuarto utilizamos la notación slicing<jupyter_code><jupyter_output><empty_output><jupyter_text>Operaciones básicas en Numpy arrayCon Numpy array podes hacer las operaciones básicasConsidera los vectores __u__ y __v__ , cada uno con dos elementosu = (1,3) v = (3,5)<jupyter_code>u = np.array([1,3])<jupyter_output><empty_output><jupyter_text>Declara el Numpy array v en la siguiente celda<jupyter_code># Crea el array v<jupyter_output><empty_output><jupyter_text>Vamos a realizar la suma de u y v y guardar el resultado en la variable c<jupyter_code>c = u + v
Veamos el resultado de c
# Corre esta celda
v<jupyter_output><empty_output><jupyter_text>¿Que sucede sí hacemos la operación u + 3 ?Veamoslo<jupyter_code>u + 3<jupyter_output><empty_output><jupyter_text>Al efectuar u +3, Numpy suma la cantidad 3 a cada entrada! Multipliquemos por un factor de 2 al vector __v__<jupyter_code>2*v
¿Cada entrada es multiplicada por dos verdad ?<jupyter_output><empty_output><jupyter_text>Producto especial producto hadamardComo sabes, los dos productos que manejamos en matemáticas para vectores son* Producto Punto entre dos vectores cuyo resultado es un escalar* Producto Cruz entre dos vectores cuyo resultado es un vectorEn ciencia de dato existe un producto especial conocido como hadamard product.Su resultado es un vector y es la multiplicación entrada por entrada de dos vectores. Por ejemplo considérese los vectores T = np.array([2,3]) N = np.array([5,8])<jupyter_code>T = np.array([2,3])
N = np.array([5,8])<jupyter_output><empty_output><jupyter_text>Veamos que pasa si hacemos T*N<jupyter_code>T*N<jupyter_output><empty_output><jupyter_text>Se forma vector, cuyas componentes sonPrimer entrada = 2*5 = 10Segunda entrada = 3*8 = 24¿Lo entendiste?  Producto PuntoTe acordas que el producto punto de dos vectores __A__ y __B__ estaba dado por : $$ \vec{A}\cdot\vec{B} = A_{1} B_{1} +  A_{2} B_{2} +  A_{3} B_{3} $$Componente por componete y sumabas para hacer un escalar Numpy te permite calcular el producto punto entre dos vectores Mediante el código __np.dot(vector 1 , vector 2)__ Hagamos el producto punto de T y N y guardemos el resultado en la variable __resultado<jupyter_code>resultado = np.dot(T,N)<jupyter_output><empty_output><jupyter_text>Imprimimos el resultado<jupyter_code>resultado<jupyter_output><empty_output><jupyter_text>Funciones  y métodos en Numpy¿Te acordás todas las funciones que venian con el módulo matemático __math__ ? Resulta ser que numpy las tiene programadas y así mismo muchas mas!  Métodos en Numpy  __Estadística__  * Promedio .mean()* Máximo   .max()* Mínimo  .min()* Desviación estardar .std()__Funciones Matemáticas en Numpy__ * Seno Aplicar funcion seno a cada componente en un array __np.sin(nombre del array)__ * Coseno Aplicar funcion coseno a cada componente en un array __np.cos(nombre del array)__ * Tangente Aplicar funcionTangente a cada componente en un array __np.tan(nombre del array)__  * Producto Multplica componente por componente __np.prod(nombre del array)__ * Para sumar todas las entradas en un vector __np.sum(nombre del array)__ * Producto cruz __np.cross( vector 1, vector 2)__ * Calcular el exponente de cada elemento del array __np.exp(nombre del vector)__ * Calcular el logaritmo natural de cada elemento del array __np.log( vector)__ * Raiz cuadradada de los elementos del vector __np.sqrt(vector)__ __Funciones para crear arrays formas mas rápida__ linspace te permite crear un array de cierta cantidad de elementes que vos definir. Por ejemplo si quiero un array de 5 elementos que se encuentren entre -2 y 2  __np.linspace(-2,2, num = 5)__ -2 y 2 son los límites de mi array num = 5 representa la cantidad de elementos que quiero en el array  Para mas funciones, podes acceder a este [enlace](https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html)  ResumenPara repasar podes usar el siguiente [video](https://youtu.be/PdQCaS9w_Qo)  Ejemplo 1. Graficando la funcion seno(x)1. Genere un vector llamado __x__ con 100 entradas con los siguientes intervalos: * inicio = 0 * final = 2*$\pi$ 2. Cree un nuevo vector __y__ donde le aplique la función seno al vector x 3. Grafique el resultado usando el módulo matploblib utilizando el siguiente código     import matploblib.pyplot as plt     %matplotlib inline     plt.plot(x,y)     Resolución. Para generar el vector x, podemos utilizar la función np.linspace.El termino $\pi$ en Numpy, puede usarse como np.pi  Paso 1. Generando el vector x<jupyter_code>x = np.linspace(0 ,  2* np.pi , 100)<jupyter_output><empty_output><jupyter_text>Paso 2. Generando el vector y<jupyter_code>y = np.sin(x)<jupyter_output><empty_output><jupyter_text>Paso 3. Gráfica<jupyter_code>import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(x,y)<jupyter_output><empty_output><jupyter_text>Preguntemos por x<jupyter_code>x<jupyter_output><empty_output><jupyter_text>Son muchos puntos o.O !!!Por eso Numpy es uno de los Paquetes mas poderoso en Python! Simplifica mucho los cálculos Te propongo el siguiente ejercicio! Los factores exponenciales son clásicos en Química, están relaciones con la distribución de Boltzmann .En Química se encuentran por todo lado! Te recordas de tus clases de Orgánica cuando hablaban de la Energía de activación habia un factor exponencial. $$ y = e^{-0.005*x}$$ * Grafica la anterior función exponencial. * Para esto crea un array llamado x que se componen por 200 elementos que van de 20 a 300 * Crea un array y que utilice la función np.exp(-0.005*x) para crear y * Grafica el resultado con plt.plot(x,y)<jupyter_code># Crea el array x con linspace
# Imprime x
x
# Crea el array y

#Imprimir y
y
#Grafica la función con el comando plt.plot(x,y)<jupyter_output><empty_output><jupyter_text>Arrays en dos y mas dimensionesConsidera la siguiente lista __a__a = [[11 ,12,13],[21,22,23],[31,32,33]]La lista a es una lista que a su vez contiene tres listas como elementos<jupyter_code>a = [[11,12,13],[21,22,23],[31,32,33]]<jupyter_output><empty_output><jupyter_text>Utilizando numpy podemos tranforma la lista en un numpy array<jupyter_code>A = np.array(a)
A<jupyter_output><empty_output><jupyter_text>Listo! Acabamos de crear un array de tres dimensiones. Basicamente es crear  listas anidadas dentro de una lista, luego convierte dicha lista en un np.array. Técnicamente es una matriz!  Para saber la forma de la matriz podemos usar el atributo __.shape__<jupyter_code>A.shape<jupyter_output><empty_output><jupyter_text>3x3 significa que tenemos tres filas o número de listas dentro de la lista y tres columnas o número de elementos dentro de las listas individuales ¿ Cuantos elementos en total tiene A ? Podes usar el atributo __.size__<jupyter_code>A.size<jupyter_output><empty_output><jupyter_text>Para acceder a los elementos de A, podemos usar la notación de index y doble index. Recuerda que $ A = (A[0] , A[1] , [A2]) $ Donde * $A[0]$ es $[11,12,13]$ * $A[1]$ es $[21,22,23]$ * $A[2]$ es $[31,32,33]$ Y su vez, para acceder a los elementos mas individuales $A = [A[0][0],A[0][1],A[0][2],A[1][0],A[1][1],A[1][2],A[2][0],A[2][1],A[2][2]]$ Lo podes ver como una matriz$$ A= \begin{bmatrix}{A[0][0]}&{A[0][1]}&{A[0][2]}\\{A[0][0]}&{A[0][1]}&{A[0][2]}\\{A[0][0]}&{A[0][1]}&{A[0][2]}\end{bmatrix} $$  SlicingLas mismas reglas que aprendiste en listas, aplican para los Numpy arrays.Por ejemplo si quiero obtener los primeros elementos de la fila 1 hago lo siguienteRecuerda la notación!A[fila][columna]<jupyter_code>A[0][0:2]<jupyter_output><empty_output><jupyter_text>Le estoy diciendo a Python. Busca en la primer fila, index cero Luego muestrame los primeros dos elementos  Busca en la tercer fila, los dos últimos elementos.<jupyter_code>A[2][1:3]<jupyter_output><empty_output><jupyter_text>Te propongo que busques en la segunda fila, los dos últimos elementos de la maxtriz<jupyter_code>#Haz el ejercicio acá<jupyter_output><empty_output><jupyter_text>Recerda que en Python en general index 0 es el primer elemento index 1 es el segundo elemento index 2 es el tercer elemento      __.__      __.__      __.__ index(N-1) es el N-enésimo elemento       Operaciones Básicas para arraysLo que aprendiste para arrays en una dimensión también se aplicar para dimensiones mayores. Hagamos el siguiente ejercicio Crea dos matrices U y VU = [[2,3],[4,5]] V = [[5,6],[4,7]]<jupyter_code># Haz el ejercicio acá
# Crea las matrices U y V acá.
# Recuerda, primeros podes definir las listas 
# Luego conviertelas en un np.array<jupyter_output><empty_output><jupyter_text>Crea una nueva matriz W, que sea la suma U + V Imprime en pantalla el resultado<jupyter_code># Crea W = U + V
#Imprime el resultado<jupyter_output><empty_output><jupyter_text>Lo viste? La suma de matrices de la misma forma __.shape__ es simplemente la suma entrada por entrada$$ W = U + V = \begin{bmatrix}{(U[0][0] + V[0][0])}&{(U[0][1] + V[0][1])}\\{(U[1][0] + V[1][0])}&{(U[1][1] + V[1][1])}\end{bmatrix} $$ $$ W = U + V  \begin{bmatrix}{W[0][0]}&{W[0][1]}\\{W[1][0]}&{W[1][1]}\end{bmatrix} $$ Osea W $$ W = U + V = \begin{bmatrix}{(2 + 5)}&{(3 + 6)}\\{(4 + 4)}&{(5 + 7)}\end{bmatrix} $$ $$ W = U + V  \begin{bmatrix}{7}&{9}\\{8}&{12}\end{bmatrix} $$  ¿Que suce si multiplicamos la matriz U por 2?<jupyter_code>2*U<jupyter_output><empty_output><jupyter_text>Productos en matricesEn matrices tenemos el productor hadamard (definido para ciencia de datos) y así mismo el producto matricial. Producto hadamard entre U y V<jupyter_code>U*V
Producto matricial entre U y V

Misma notación que e producto punto entre vectores, pero dando como resultado una matriz
Z = np.dot(U,V)
print(Z)<jupyter_output><empty_output><jupyter_text>ResumenPara repasar matrices te recomendamos este [vídeo](https://youtu.be/mskohEk97oc) [Volver al índice](https://notebooks.azure.com/jeff0994/libraries/QU-0560-1-1/html/01Introducci%C3%B3n-Indice.ipynb)<jupyter_code><jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10061936325111377, 'fraction_numerical': 0.020536781484298598, 'mean_word_length': 5.378031878031878, 'pattern_counts': {'":': 0, '<': 171, '<?xml version=': 0, '>': 171, 'https://': 4, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '42', 'dir': 'jupyter-structured-clean-dedup', 'id': '397207', 'license': 'MIT', 'n_tokens_mistral': 6844, 'n_tokens_neox': 6687, 'n_words': 2292, 'path': '05PrincipiosParteIIICálculosCientíficosNumpy.ipynb', 'repo_name': 'jeff0994/Qu-00560', 'size': '18423'}