Saltar al contenido principal
LibreTexts Español

1.3: Modularización del Código

  • Page ID
    125230
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)

    \( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)

    ( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)

    \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)

    \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)

    \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)

    \( \newcommand{\Span}{\mathrm{span}}\)

    \( \newcommand{\id}{\mathrm{id}}\)

    \( \newcommand{\Span}{\mathrm{span}}\)

    \( \newcommand{\kernel}{\mathrm{null}\,}\)

    \( \newcommand{\range}{\mathrm{range}\,}\)

    \( \newcommand{\RealPart}{\mathrm{Re}}\)

    \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)

    \( \newcommand{\Argument}{\mathrm{Arg}}\)

    \( \newcommand{\norm}[1]{\| #1 \|}\)

    \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)

    \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    \( \newcommand{\vectorA}[1]{\vec{#1}}      % arrow\)

    \( \newcommand{\vectorAt}[1]{\vec{\text{#1}}}      % arrow\)

    \( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \( \newcommand{\vectorC}[1]{\textbf{#1}} \)

    \( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)

    \( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)

    \( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)

    \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)

    1.3.1: Diseñar una función potencial

    Podríamos seguir alterando el código anterior de una manera sencilla. Por ejemplo, podríamos agregar más partículas agregando variables x1, x2, q1, q2, y así sucesivamente, y alterando nuestra fórmula para calcular phi. Sin embargo, esto no es muy satisfactorio: cada vez que queremos considerar una nueva colección de posiciones o cargas de partículas, o cambiar el número de partículas, tendríamos que reescribir la “lógica” interna del programa, es decir, la parte que calcula los potenciales. En la terminología de programación, nuestro programa es insuficientemente “modular”. Idealmente, queremos aislar la parte del programa que calcula el potencial de la parte que especifica las entradas numéricas para el cálculo, como las posiciones y los cargos.

    Para modular el código, definamos una función que calcula el potencial de un conjunto arbitrario de partículas cargadas, muestreadas en un conjunto arbitrario de posiciones. Tal función necesitaría tres conjuntos de entradas:

    • Una matriz de posiciones de partículas\(\vec{x}\equiv [x_{0}, \cdots, x_{N-1}]\). (No te confundas, por cierto: estamos usando estos\(N\) números para referirnos a las posiciones de las\(N\) partículas en un espacio 1D, no a la posición de una sola partícula en un espacio\(N\) -dimensional.)
    • Una matriz de cargas de partículas\(\vec{q}\equiv [q_{0}, \cdots, q_{N-1}]\).
    • Una matriz de puntos de muestreo\(\vec{X}\equiv [X_{0}, \cdots, X_{M-1}]\), que son los puntos donde queremos conocer\(\phi(X)\).

    El número de partículas\(N\), y el número de puntos de muestreo,\(M\), deben ser enteros positivos arbitrarios. Además,\(N\) y no\(M\) es necesario que sean iguales.

    La función que pretendemos escribir debe calcular la matriz

    \[\begin{bmatrix}\phi(X_{0})\\ \phi(X_{1}) \\ \vdots \\ \phi(X_{M-1})\end{bmatrix}\]

    que contiene el valor del potencial eléctrico total en cada uno de los puntos de muestreo. El potencial total se puede escribir como la suma de contribuciones de todas las partículas. Definamos\(\phi_{j}(x)\) como el potencial que produce la partícula\(j\):

    \[\phi_{j(x)}\equiv\frac{q_{j}}{|x-x_{j}|}\]

    Entonces el potencial total es

    \[\begin{bmatrix}\phi(X_0)\\ \phi(X_1) \\ \vdots \\ \phi(X_{M-1})\end{bmatrix} = \begin{bmatrix}\phi_0(X_0)\\ \phi_0(X_1) \\ \vdots \\ \phi_0(X_{M-1})\end{bmatrix} + \begin{bmatrix}\phi_1(X_0)\\ \phi_1(X_1) \\ \vdots \\ \phi_1(X_{M-1})\end{bmatrix} + \cdots + \begin{bmatrix}\phi_{N-1}(X_0)\\ \phi_{N-1}(X_1) \\ \vdots \\ \phi_{N-1}(X_{M-1})\end{bmatrix}.\]

    1.3.2 Redacción del Programa

    Vamos a codificar esto. Regresar al archivo potentials.py, y eliminar todo el contenido del archivo. Luego sustitúyalo por lo siguiente:

    from scipy import *
    import matplotlib.pyplot as plt
    
    ## Return the potential at measurement points X, due to particles
    ## at positions xc and charges qc.  xc, qc, and X must be 1D arrays,
    ## with xc and qc of equal length.  The return value is an array
    ## of the same length as X, containing the potentials at each X point.
    def potential(xc, qc, X):
        M = len(X)
        N = len(xc)
        phi = zeros(M)
        for j in range(N):
            phi += qc[j] / abs(X - xc[j])
        return phi
    
    charges_x  = array([0.2, -0.2])
    charges_q  = array([1.5, -0.1])
    xplot = linspace(-3, 3, 500)
    
    phi = potential(charges_x, charges_q, xplot)
    
    plt.plot(xplot, phi)
    pmin, pmax = -50, 50
    plt.ylim(pmin, pmax)
    plt.show()
    
    clipboard_e4adc3c5073e950f6c32016d35b4b68ef.png
    Figura\(\PageIndex{1}\)

    Al escribir o pegar lo anterior en su archivo, asegúrese de conservar la sangría (es decir, el número de espacios al comienzo de cada línea). La sangría es importante en Python; como veremos, se utiliza para determinar la estructura del programa. Ahora guarda y ejecuta el programa de nuevo:

    • En la GUI de Windows, escriba F5 en la ventana de edición que muestra potentials.py.
    • En GNU/Linux, escriba python -i potentials.py desde la línea de comandos.
    • Alternativamente, desde la línea de comandos de Python, escriba import potential, que cargará y ejecutará su archivo potentials.py.

    Ahora debería ver una figura como la de la derecha, mostrando el potencial eléctrico producido por dos partículas, una en posición\(x_{0}=0.2\) con carga\(q_{0}=1.5\) y otra en posición\(x_{1}=-0.2\) con carga\(q_{1}=-0.1\).

    Hay menos que\(20\) líneas de código real en el programa anterior, pero hacen bastantes cosas. Vamos a repasarlos a su vez:

    Importaciones de módulos

    Las dos primeras líneas importan los módulos Scipy y Matplotlib, para su uso en nuestro programa. Aún no hemos explicado cómo funciona la importación, así que hagámoslo ahora.

    from scipy import *
    import matplotlib.pyplot as plt
    

    Cada módulo Python, incluyendo Scipy y Matplotlib, define una variedad de funciones y variables. Si usas varios módulos, podrías tener una situación en la que, digamos, dos módulos diferentes definen cada uno una función con el mismo nombre, pero haciendo cosas completamente diferentes. Eso sería muy malo. Para ayudar a evitar esto, Python implementa un concepto llamado espacio de nombres. Supongamos que importa un módulo (digamos Scipy) como este:

    import scipy
    

    Una de las funciones definidas por Scipy es linspace, que ya hemos visto. Esta función fue definida por el módulo scipy, y se encuentra dentro del espacio de nombres scipy. Como resultado, cuando importas el módulo Scipy usando la línea import scipy, tienes que llamar a la función linspace así:

    x = scipy.linspace(-3, 3, 500)
    

    El scipy. en front dice que te estás refiriendo a la función linspace que se definió en el espacio de nombres scipy. (Nota: la documentación en línea para linspace se refiere a ella como numpy.linspace, pero exactamente la misma función también está presente en el espacio de nombres scipy. De hecho, todos numpy. * las funciones se replican en el espacio de nombres scipy. Entonces, a menos que se indique lo contrario, sólo tenemos que importar scipy.)

    Estaremos usando muchas funciones que se definen en el espacio de nombres scipy. Ya que sería molesto tener que seguir escribiendo scipy. por todas partes, optamos por usar una declaración de importación ligeramente diferente:

    from scipy import *
    

    Esto importa todas las funciones y variables en el espacio de nombres scipy directamente en el espacio de nombres de su programa. Por lo tanto, se puede simplemente llamar linspace, sin el scipy. prefijo. Obviamente, no quieres hacer esto por cada módulo que uses, ¡de lo contrario terminarás con el problema de choque de nombres al que aludimos antes! El único módulo con el que usaremos este atajo es scipy.

    Otra forma de evitar tener que escribir prefijos largos se muestra con esta línea:

    import matplotlib.pyplot as plt
    

    Esto importa el módulo matplotlib.pyplot (es decir, el módulo pyplot que está anidado dentro del módulo matplotlib). Ahí es donde se definen las funciones de trazado, show y otras funciones de trazado. El as plt en la línea anterior dice que nos referiremos al espacio de nombres matplotlib.pyplot como el formato corto plt en su lugar. Por lo tanto, en lugar de llamar a la función plot así:

    matplotlib.pyplot.plot(x, y)
    

    lo llamaremos así:

    plt.plot(x, y)

    Comentarios

    Volvamos al programa que estábamos viendo antes. Las siguientes líneas, comenzando con #, son “comentarios”. Python ignora el carácter # y todo lo que le sigue, hasta el final de la línea. Los comentarios son muy importantes, incluso en programas sencillos como este.

    Cuando escribas tus propios programas, por favor recuerda incluir comentarios. No necesitas un comentario para cada línea de código, eso sería excesivo, pero como mínimo, cada función debería tener un comentario explicando qué hace y cuáles son las entradas y los valores de retorno.

    Definición de función

    Ahora llegamos a la definición de función para la función llamada potential, que es la función que calcula el potencial:

    def potential(xc, qc, X):
        M = len(X)
        N = len(xc)
        phi = zeros(M)
        for j in range(N):
            phi += qc[j] / abs(X - xc[j])
        return phi
    

    La primera línea, comenzando con def, es un encabezado de función. Este encabezado de función indica que la función se denomina potencial, y que tiene tres entradas. En la terminología informática, las entradas que acepta una función se denominan parámetros. Aquí, los parámetros se denominan xc, qc y X. Como explican los comentarios, pretendemos utilizarlos para las posiciones de las partículas, las cargas de las partículas y las posiciones en las que medir el potencial, respectivamente.

    La definición de función consiste en el encabezado de la función, junto el resto de las líneas sangradas debajo de ella. La definición de la función termina una vez que llegamos a una línea que está en el mismo nivel de sangría que el encabezado de la función. (Esa línea de terminación se considera una línea de código separada, que no forma parte de la definición de la función).

    Por convención, se deben utilizar 4 espacios por nivel de sangría.

    Las líneas sangradas debajo del encabezado de la función se llaman el cuerpo de la función. Este es el código que se ejecuta cada vez que se llama a la función. En este caso, el cuerpo de función consta de seis líneas de código, las cuales están destinadas a calcular el potencial eléctrico total, de acuerdo con el procedimiento que hemos esbozado en el apartado anterior:

    • Las dos primeras líneas definen dos variables útiles, M y N. Sus valores se establecen en las longitudes de las matrices X y xc, respectivamente.
    • La siguiente línea llama a la función ceros. La entrada a ceros es M, la longitud de la matriz X (es decir, el tercer parámetro de nuestra función). Por lo tanto, ceros devuelve una matriz, de la misma longitud que X, con cada elemento establecido en 0.0. Por ahora, esto representa el potencial eléctrico ante la falta de cargas. Le damos a esta matriz el nombre phi.
    • La función luego itera sobre cada una de las partículas y suma su contribución al potencial, utilizando una construcción conocida como bucle for. El código para j in range (N): es la “línea de cabecera” del bucle, y la siguiente línea, sangría 4 espacios más profundos que la línea de cabecera, es el “cuerpo” del bucle.

      La línea de cabecera establece que debemos ejecutar el cuerpo del bucle varias veces, con la variable j establecida en diferentes valores durante cada ejecución. Los valores de j a loop over están dados por range (N). Esta es una llamada de función a la función range, con N (el número de cargas eléctricas) como entrada. La llamada a la función range (N) devuelve una secuencia que especifica N enteros sucesivos, de 0 a N-1, inclusive. (Obsérvese que el último valor de la secuencia es N-1, no N. Debido a que partimos de 0, esto significa que hay un total de N enteros en la secuencia. Además, el rango de llamadas (N) es el mismo que el rango de llamadas (0, N).)

    • Para cada j, calculamos qc [j]/abs (X - xc [j]). Se trata de una matriz cuyos elementos son los valores del potencial eléctrico en el conjunto de posiciones X, que surgen de la partícula individual\(j\). En términos matemáticos, estamos calculando
      \[\phi_j(X) \equiv \frac{q_j}{|X - x_j|}\]

      utilizando la matriz de posiciones X. Luego agregamos esta matriz a phi. Una vez hecho esto para todos j, la matriz phi contendrá el potencial total deseado,
      \[\phi(X) = \sum_{j=0}^{N-1}\phi_j(X).\]

    • Finalmente, llamamos return para especificar la salida de la función, o valor de retorno. Esta es la matriz phi.

    Código de nivel superior: Constantes numéricas

    Después de la definición de la función viene el código para usar la función:

    charges_x  = array([0.2, -0.2])
    charges_q  = array([1.5, -0.1])
    xplot = linspace(-3, 3, 500)
    

    Al igual que las declaraciones de importación al inicio del programa, estas líneas de código se encuentran en el nivel superior, es decir, no tienen sangría. El encabezado de la función que define la función potencial también está en el nivel superior. Ejecutar un programa Python consiste en ejecutar su código de nivel superior, en secuencia.

    Las líneas anteriores definen variables para almacenar algunas constantes numéricas. En las dos primeras líneas, las variables charges_x y charges_q almacenan los valores numéricos de las posiciones y cargos que nos interesan. Estos se inicializan usando la función array. Quizás se esté preguntando por qué la llamada a la función array tiene corchetes anidados en comas. Te explicaremos más adelante, en la parte 2 del tutorial.

    En la tercera línea, la llamada a la función linspace devuelve una matriz, cuyo contenido se inicializa a los 500 números entre -3 y 3 (inclusive).

    A continuación, llamamos a la función potencial, pasando charges_x, charges_q y xplot como las entradas:

    phi = potential(charges_x, charges_q, xplot)
    

    Estas entradas proporcionan los valores de los parámetros xc, qc y X de la definición de función respectivamente. El valor de retorno de la llamada a la función es una matriz que contiene el potencial total, evaluado en cada una de las posiciones especificadas en xplot. Este valor de retorno se almacena como la variable llamada phi.

    Trazado

    Finalmente, creamos la parcela:

    plt.plot(xplot, phi)
    pmin, pmax = -50, 50
    plt.ylim(pmin, pmax)
    plt.show()
    

    Ya hemos visto cómo funcionan las funciones de trama y espectáculo. Aquí, antes de llamar a plt.show, hemos agregado dos líneas adicionales para hacer que la curva de potencial sea más legible, ajustando los límites del eje y de la trama. La función ylim acepta dos parámetros, los límites inferior y superior del eje y. En este caso, establecemos los límites en -50 y 50 respectivamente. Hay una función xlim para hacer lo mismo para el eje x.

    Observe que en la línea pmin, pmax = -50, 50, establecemos dos variables (pmin y pmax) en la misma línea. Esto es un poco de “azúcar sintáctico” para que el código sea un poco más fácil de leer. Es equivalente a tener dos líneas separadas, así:

    pmin = -50
    pmax = 50
    

    Explicaremos cómo funciona este constructo en la siguiente parte del tutorial.


    This page titled 1.3: Modularización del Código is shared under a CC BY-SA 4.0 license and was authored, remixed, and/or curated by Y. D. Chong via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.