Subrutinas

Manejo de Cálculos Repetitivos

En muchos programas, en particular en algunos muy largos y complejos donde varias veces se rehacen los mismos cálculos una y otra vez podrían utilizarse funciones externas para simplificar el código y volverlo más simple y compacto ¿Pero qué pasa si lo que se quiere calcular no tiene estructura de función? Esto pasaría cuando tengo un problema a resolver donde la solución no es un sólo resultado, sino que la solución serían varios valores (por ejemplo coordenadas y velocidades), por lo cual una función no me serviría. Resumiendo, necesito un subprograma parecido la función externa, pero que a diferencia de la función me devuelva varios resultados.
Para este tipo de trabajo se diseñaron las Subrutinas, que tienen las siguientes propiedades y formas de uso:

Uso de Subrutinas

Esquema de como las variables de los argumentos de la llamada a una subrutina son reasignados a las variables de esta. Note que cada variable debe ser del mismo tipo y dimensionalidad que en el programa principal. La variable E de la subrutina desconoce que existe una variable también llamada E en el programa principal.

Veamos como funciona en un caso real, donde el programa Prueba llama a la subrutina TEST
\(\ \ \ \ \ \ \ \) Program Prueba

\(\ \ \ \ \ \ \ \) REAL*4 C(100)

\(\ \ \ \ \ \ \ \) Integer B

\(\ \ \ \ \ \ \ \) \(\vdots\)

\(\ \ \ \ \ \ \ \) CALL TEST(A, B, C)

\(\ \ \ \ \ \ \ \) \(\vdots\)

\(\ \ \ \ \ \ \ \) END
\(\ \ \ \ \ \ \ \) SUBROUTINE TEST(X,Y,Z)

\(\ \ \ \ \ \ \ \) REAL*4 Z(100)

\(\ \ \ \ \ \ \ \) Integer Y

\(\ \ \ \ \ \ \ \) \(\vdots\)

\(\ \ \ \ \ \ \ \) RETURN

\(\ \ \ \ \ \ \ \) END
En la figura 1.1 podemos ver un esquema de como las variables son transferidas del programa principal a la subrutina, para el caso del ejemplo que acabamos de ver. Notar que sólo A, B y C que son los argumentos de la llamada a la subrutina en el programa Prueba y son las únicas variables que son copiadas a la subrutina. Otras variables del programa como D, E y X no son transferidas y su existencia será desconocida por la subrutina. Las variables A, B y C son transferidas a un nuevo espacio de memoria como las variables X, Y y Z. Como B es una variable entera, sólo puede ser transferida a otra variable entera, por eso Y es definida como entera también en la subrutina. Y en el caso del vector C de 100 elementos tiene que ser recibida por una variable de igual dimensión y tipo, en este caso Z que fue definida correctamente para el caso en la subrutina Test. Note que el orden en los argumentos en el programa principal es concordante con la variable elegida en la subrutina (de igual tipo y dimensionalidad).
Cuando una subrutina se encuentra con la sentencia RETURN termina su trabajo y retorna las variables que están en los argumentos al programa principal, volviendo a reasignar los valores. En este caso las variables X, Y y Z se copian a A, B, y C. Las variables G y E de la subrutina test se pierden cuando esta termina y se las considera variables temporales2. En cambio en el programa Prueba las variables D, E y X nunca se enteraron de la llamada a Test, a pesar de que en la subrutina había dos variables con igual nombre (X y E).

Si, en cambio, es posible hacer lo siguiente:
\(\ \ \ \ \ \ \ \) CALL TEST(A+2.5, B, C)
En este ejemplo, se hace la cuenta A+2.5 y el resultado se envía a la subrutina y es asignado a la variable X. Pero en este caso en particular inhibimos a la variable A de recibir cualquier resultado de producto de la ejecución de la subrutina.
Resumiendo, la subrutina cuando se ejecuta crea su propio universo de variables y sólo recibe datos por los argumentos de la llamada. Este universo de variables desaparece cuando la subrutina termina. Si la subrutina es vuelta a llamar el espacio de memoria se crea nuevamente, pero los datos en las variables anteriores no se conservan.

Ejemplos de Subrutinas

Como ejemplos realizaremos dos subrutinas que se encarguen de la transformación de coordenadas polares a cartesianas y viceversa, junto con un programa que las llama y realiza ambas transformaciones.

\(\ \ \ \ \ \ \ \)program polares

\(\ \ \ \ \ \ \ \)pi=atan(1)*4

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)write(*,*)’Ingrese X,Y’

\(\ \ \ \ \ \ \ \)read(*,*) X,Y

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)call pola(X,Y,r,theta)

\(\ \ \ \ \ \ \ \)write(*,*)’R =’,r,’Theta =’, theta/pi*180

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)call cartesianas(X,Y,r,theta)

\(\ \ \ \ \ \ \ \)write(*,*)’x =’,X,’y =’,Y

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)end

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)Subroutine pola(x,y,r,theta)

\(\ \ \ \ \ \ \ \)r = sqrt(x*x + y*y)

\(\ \ \ \ \ \ \ \)theta = atan2(y,x)

\(\ \ \ \ \ \ \ \)return

\(\ \ \ \ \ \ \ \)end

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)Subroutine cartesianas(x,y,r,theta)

\(\ \ \ \ \ \ \ \)x=r*cos(theta)

\(\ \ \ \ \ \ \ \)y=r*sin(theta)

\(\ \ \ \ \ \ \ \)return

\(\ \ \ \ \ \ \ \)end

Programando con subrutinas

Las subrutinas traen aparejado muchas mejoras para resolver problemas complejos que suelen generar programas grandes y largos. Esto se debe a que permiten dividir el problema grande en muchos pequeños cuyo solución es más fácil de manejar y testear. Existen libros3 y muchas páginas web con subrutinas ya programadas que abarcan casi todas las áreas de la matemática. Esto no sólo es cierto para algoritmos matemáticos simples, sino que también para los muy complejos. Desarrollar algoritmos para computadoras es toda una rama de la matemática contemporánea. El uso masivo de computadoras creó a su vez otros campos en el desarrollo de la matemática. En capítulos siguientes trataremos con ejemplo dos de estos campos, el de ordenar y el de generar modelos con números al azar.

¿Cuáles son las metas de estas nuevas áreas de la matemática asociada a la computación? En si, resolver los problemas con algoritmos novedosos o variantes que tengan las siguientes propiedades:

Por otro lado, hay otra serie de ventajas de usar en forma masiva subrutinas para resolver un problema, que también son dignas de mención:

Función Externa y uso de funciones en una subrutina

Las funciones pueden definirse como external haciendo:
External nombre_de_la_función
Esta orden tiene dos formas de trabajo muy diferentes. La primera de ellas es la de reemplazar una función intrínseca como por ejemplo puede ser el cos(), por una creada por el propio usuario. Es decir, si se trabaja en una computadora cuyo compilador Fortran no tiene una función cos() que satisfaga los requerimientos necesarios (por ejemplo: precisión en los decimales). Esta puede ser reemplazada por una propia, programada por el usuario. Con poner la orden, tal como está en el ejemplo, ya no se llama a la función intrínseca sino a la construida por el usuario. Se podría evitar hacer esto, por ejemplo, poniéndolo otro nombre, como COS_mio() pero había que editar todo el programa para buscar las llamadas de cos() y reemplazarlas por COS_mio(). En cambio, de esta manera, con EXTERNAL, no hay que hacer ningún reemplazo. El usuario es ahora el dueño del nombre cos() para su función.
La otra función de la orden EXTERNAL es declarar que una función puede ser parte de los argumentos en el llamado de una subrutina, y por lo tanto la subrutina queda habilitada para usar la función al recibirla en los argumentos como una variable mas.
\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)PROGRAM AREA

\(\ \ \ \ \ \ \ \)EXTERNAL FUN

\(\ \ \ \ \ \ \ \)\(\vdots\)

\(\ \ \ \ \ \ \ \)CALL RUNGE (FUN, LOW, HIGH, AREA2 )

\(\ \ \ \ \ \ \ \)\(\vdots\)

\(\ \ \ \ \ \ \ \)END

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)FUNCTION FUN( X )

\(\ \ \ \ \ \ \ \)\(\vdots\)

\(\ \ \ \ \ \ \ \)RETURN

\(\ \ \ \ \ \ \ \)END

\(\ \ \ \ \ \ \ \)

\(\ \ \ \ \ \ \ \)SUBROUTINE RUNGE ( F, X0, X1, A )

\(\ \ \ \ \ \ \ \)\(\vdots\)

\(\ \ \ \ \ \ \ \)RETURN

\(\ \ \ \ \ \ \ \)END

Sentencia Common - Include

Hasta ahora hemos visto que la única forma de transferir datos entre un programa y una subrutina es a través de los argumentos que se encuentra en la llamada a la subrutina. Pero forma tiene una limitación a pocas variables, y por ello no es el único método. Cuando las variables en los argumentos es muy grande se prefiere enviarlos a través de la sentencia COMMON usada en combinación con la orden INCLUDE.

Para utilizar este comando en la zona de definición de variables debo indicar el COMMON que defino con su nombre4 y sus variables asociadas. La forma general de la definición sería:
\(\ \ \ \ \ \ \ \) COMMON /NOMBRE1/ Lista de variables

\(\ \ \ \ \ \ \ \) COMMON /NOMBRE2/ Lista de variables
Ejemplo:
\(\ \ \ \ \ \ \ \) COMMON /listado1/ A,B,C,IK,X(1000)

\(\ \ \ \ \ \ \ \) COMMON /listado2/ B1,B2,B3,B4
y en las subrutinas tendría poner:
\(\ \ \ \ \ \ \ \) SUBROUTINE SUB1()

\(\ \ \ \ \ \ \ \) COMMON /listado1/ X,Y,Z,J,ES(1000)

\(\ \ \ \ \ \ \ \) \(\vdots\)

\(\ \ \ \ \ \ \ \) RETURN

\(\ \ \ \ \ \ \ \) END
Vemos que esta subrutina recibe también como argumentos las variables del COMMON listado1
Pero la segunda podría ser asi:
\(\ \ \ \ \ \ \ \) SUBROUTINE SUB2()

\(\ \ \ \ \ \ \ \) COMMON /listado1/ X,Y,Z,J,ES(1000)

\(\ \ \ \ \ \ \ \) COMMON /listado2/ B1,B2,B3,B4

\(\ \ \ \ \ \ \ \) \(\vdots\)

\(\ \ \ \ \ \ \ \) RETURN

\(\ \ \ \ \ \ \ \) END
Es decir la SUB1 recibe los argumentos del primer COMMON, mientras la segunda SUB2 recibe los argumentos de ambos COMMONs: listado1 y listado2. Una tercera subrutina podría no recibir ninguno de los dos listados de variables, es decir, no tendría ninguna de las sentencias COMMON en su código.
La sentencia INCLUDE “nombre_de_un_archivo" hace que el compilador cargue y compile la secuencia de Fortran que está escrita en el archivo nombrado, en ese lugar del programa. Es una práctica común poner muchas de las definiciones de variables y COMMON´s en un archivo aparte y que este sea incluido por el compilador, tanto en el programa principal, como en las subrutinas. Esto permite que no haya diferencias entre las definiciones de las variables que se comparten como argumentos entre el programa principal y sus subprogramas. Los archivos que tienen esta información (y que va a ser incluida) suelen tener terminación “.h” como norma.

Recursión

Se denomina recursión cuando una subrutina se llama a si misma. Esto tiene sentido cuando un problema es posible reducirlo en un orden de complejidad pero sigue siendo el mismo problema. Esta situación se da en algoritmos tipo diagrama de árbol, que son muy usados, por ejemplo, en programas de cálculo de la evolución dinámica de las estrellas en una galaxia.
Para ver un ejemplo real y simple estudiaremos el factorial, que cumple con las propiedades que hemos descripto. Recordar que factorial de N, se puede escribir como N!=N(N-1)!. Es decir convierto el factorial de N en resolver ahora el factorial de (N-1)!. Y entonces podría repetir el procedimiento hasta que mi problema quede reducido al factorial de 1 que por definición 1!=1.
Por otro lado hay que recordar que cuando una subrutina es llamada, crea su propio universo de variables que no son ni las del programa principal, ni el de las otras subrutinas. Incluso, cuando una subrutina se llama así misma crea otra zona memoria para sus variables, que no se comparte contra su propia versión iniclal. Es decir la subrutina madre no comparte variables con ls subrutina hija, salvo las que se reasignan porque están en los argumentos del llamado.
Podemos entonces hacer un programa muy compacto que resuelva el factorial usando una subrutina recursiva y sería así:
\(\ \ \ \ \ \ \ \) program factor

\(\ \ \ \ \ \ \ \) write(*,*)’Cual es el numero:’

\(\ \ \ \ \ \ \ \) read(*,*) n

\(\ \ \ \ \ \ \ \) call factorial(n,p)

\(\ \ \ \ \ \ \ \) write(*,*) p

\(\ \ \ \ \ \ \ \) end
\(\ \ \ \ \ \ \ \) Recursive Subroutine factorial(n,p)

\(\ \ \ \ \ \ \ \) if(n.gt.1) then

\(\ \ \ \ \ \ \ \ \ \ \ \ \ \) call factorial(n-1,p)

\(\ \ \ \ \ \ \ \ \ \ \ \ \ \) p=p*n

\(\ \ \ \ \ \ \ \) else

\(\ \ \ \ \ \ \ \ \ \ \ \ \ \) p=1

\(\ \ \ \ \ \ \ \) endif

\(\ \ \ \ \ \ \ \) return

\(\ \ \ \ \ \ \ \) end
En este caso en particular para el compilador GFORTRAN debo indicar que la subrutina es recursiva con el aviso de RECURSIVE en el nombre de la subrutina, pero esto puede cambiar según el compilador Fortran que se use.

Save

La función SAVE se usa en la subrutinas de la siguiente manera:
\(\ \ \ \ \ \ \ \) SAVE lista de variables
Esta sentencia se agrega al principio de la subrutina e indica cuales variables se conserven cuando la subrutina se cierra y los valores guardados sirven para ser usados en el próximo llamado. Esto destruye el modelo del uso de memoria que hemos descriptos (es una forma antigua de programar en Fortran) y su uso inhibe la posibilidad de hacer recursiones. Esta sentencia vuelve a un uso de memoria más antiguo donde la subrutinas podían volver llamarse y las variables conservaban los valores de la llamada anterior.


  1. En Fortran 90/95 es posible indicar variables que van ser modificadas por la subrutina, variables que no lo serán (inmutables), e incluso en variables que sólo tendrán los resultados de su ejecución. Pero esta facilidad es opcional y debe ser indicada explícitamente en el código.↩︎

  2. A menos que se haya indicado alguna de ellas con el comando SAVE, que veremos más adelante↩︎

  3. Veremos en este curso el libro Numerical Recipes, William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery, Este libro se puede consultar aquí↩︎

  4. Puede no tener nombre, pero entonces no puedo poner más de una de estas sentencias↩︎