En Fortran hay funciones preprogramadas (intrínsecas) que ya las hemos visto: raíz cuadrada, funciones trigonométricas, etc. Pero además de estas funciones se le permite al programador crear las suyas. Existen dos métodos para construir funciones en Fortran: a través de la Función Sentencia o de la Función Externa.
Es muy útil poder crear funciones, ahorra tiempo, hace los programas más compactos, enfocados y elegantes. Además permite que uno utilice funciones que ya se encuentran disponibles en libros o en páginas de internet. Sobre esto último, existen sitios web donde se discute cuál es el mejor algoritmo para una tarea determinada y cuales son sus mejores implementaciones en distintos lenguajes. Las funciones han tomado históricamente distintos nombres (procedimientos, subprogramas, etc) en distintos lenguajes de programación pero siempre existe una manera de definirlas.
Consiste en una sola sentencia aritmética definida en el programa. Esta estructura existe en otros lenguajes, por ejemplo, en Python se la llama función Lambda.
Se las define al comienzo del programa dándole un nombre a la función y se las construye usando variables ficticias, es decir que no son las variables del programa, sólo se las usa como referencia para construir la fórmula matemática de la función y sólo valen en la sentencia. Es decir, para indicar el orden y en que lugar se encuentran las distintas variables de la fórmula a calcular.
La forma sería la siguiente:
función(var1,var,2,var3...) = expresión matemática usando las variables var1, var2, var3,...
Donde función es el nombre que se le da a la función, las var1,var2, var3, etc son las variables que se usan en la expresión matemática. Estas variables indican el orden de los argumentos con los que defino la función. Por eso su orden es muy importante en el momento de llamarla.
Ejemplos:
Si se necesita una función discriminante (para ecuaciones cuadráticas), la puedo construir de esta manera al comienzo del programa (donde defino los tipos de variables del programa) escribiendo:
DISC(A,B,C) = B**2-4.*A*C
Entonces en el programa se puede escribir:
Program prueba_de_funcion_sentencia
DISC(A,B,C) = B**2-4.*A*C
\(\vdots\)
X= DISC(24.5, 34.5, 67.8) +25.4*C3+MAG
\(\vdots\)
MAGROJA = SQRT(DISC(z1+3, 45.5*8, (z2+z4+f6)+28)
\(\vdots\)
Detalles a tener en cuenta:
Las variables (var1,var2,...) pueden ser de cualquier tipo incluyendo variables lógicas o de caracteres.
Las funciones para el programa son variables, es decir la función es una variable que cuando se llama realiza un cálculo y entrega el resultado de su ejecución, en vez de ir a buscar un número que este guardado en la memoria. Por lo tanto una función, ya que es una variable, puede ser integer, real*4, real*8, complex, character o logical, etc.
De los cursos de análisis matemático uno adquiere la idea de que la única manera de resolver los problemas pasa por obtener siempre su solución analítica. Pero en la vida real, esa situación es más bien rara, o imposible. Incluyendo el hecho que en el caso particular de una integral puede que esta no tenga primitiva o que sea muy complejo o laborioso encontrarla. Más aún si lo que se lo quiere resolver es una ecuación diferencial. Por esta razón, en problemas muy complejos se usan los sistemas de computación con el fin aproximar la solución, es decir obtener una solución numérica. Esta puede ser en algunos casos una solución exactas o a veces aproximada. Desarrollaremos un caso en particular como ejemplo.
Uno de los métodos más simples de aproximar numéricamente el resultado de una integral definida es el método de los trapecios. Vamos a ver con un ejemplo cómo funciona este método desde el punto de vista computacional. Un tratamiento más profundo de sus propiedades incluyendo ventajas y desventajas se estudia en cursos de análisis numérico.
Con este método lo que se desea es integrar la función \(f(x)\) en el intervalo \([a,b]\), es decir queremos medir el área bajo la curva. Para ello dividiremos el intervalo en varios subintervalos más pequeños y veremos cómo se podría aproximar con un trapecio al área bajo la curva de la función \(f(x)\). Ya que la idea del método es dividir el área en \(n\) subintervalos de tamaño h, por lo que \(h = (b-a)/n\). Si llamo \(x_i\) a los puntos que separan cada uno de estos subintervalos tendríamos que:
\(a=x_0\), \(x_i=x_0+ih\) y \(b= x_n\)
En la figura 1.1 veamos cómo sería esta situación con un dibujo de la función \(f(x)\) entre los límites \(x_i\) y \(x_{i+1}\), (donde \(x_{i+1} = x_i + h\)).
:
Entonces, el área para el trapecio del la figura es:
\[A_i = h f(x_i) + \frac{h}{2} [f(x_{i+1}) - f(x_i)] = \frac{h}{2} [f(x_{i+1}) + f(x_i)]\]
y la aproximación a la integral será la suma de todos los trapecios \(A_i\)
\[Integral = \sum_{i=0}^{n}{ A_i}\]
si hago todos lo reemplazos, obtengo:1
\[Integral = \frac{h}{2}[ f(a)+f(b)] + h \sum_{i=1}^{n-1}f(x_i)\]
Veamos el programa que haría este cálculo y tomemos con función \(f(x)=x^2\)
y la integraremos en el intervalo [0,1]
En forma teórica:
\[\int_{0}^{1} x^2 dx = 1/3\]
Veamos como sería un programa y la precisión de los resultados.
\(\ \ \ \ \ \ \ \) program x2
\(\ \ \ \ \ \ \ \) real*8 integral
\(\ \ \ \ \ \ \ \) f(z)=z*z
\(\ \ \ \ \ \ \ \)
\(\ \ \ \ \ \ \ \) x0=0.
\(\ \ \ \ \ \ \ \) xn=1.
\(\ \ \ \ \ \ \ \) read(*,*)n
\(\ \ \ \ \ \ \ \) h=(xn-x0)/n
\(\ \ \ \ \ \ \ \) integral=0.
\(\ \ \ \ \ \ \ \)
\(\ \ \ \ \ \ \ \) do i=1,n-1
\(\ \ \ \ \ \ \ \ \ \ \ \) x=i*h
\(\ \ \ \ \ \ \ \ \ \ \ \) integral=integral+f(x)
\(\ \ \ \ \ \ \ \) enddo
\(\ \ \ \ \ \ \ \)
\(\ \ \ \ \ \ \ \) integral=integral*h+(f(xn)+f(x0))*h/2
\(\ \ \ \ \ \ \ \) write(*,*)’Para ’, n,’ intervalos, la integral es=’,integral
\(\ \ \ \ \ \ \ \) end
Si corro este programa para distintos valores de la cantidad de intervalos, obtengo los resultados del tabla 1.1. Como puede verse en la tabla no sirve pensar que se puede llegar al límite infinito sumando una cantidad gigantesca de trapecios debido a los errores de redondeo. Un buen resultado se obtiene cerca del 1,000,000 de trapecios, tomando intervalos más pequeños el resultado se deteriora. Pero por otro lado, la tabla nos muestra que según lo que se requiera de precisión en el resultado hay formas de evaluar este problema, y además de encontrar y determinar un resultado aproximado muy bueno incluyendo alguna idea de la precisión. En este caso hemos obtenido la integral con un error \(10^{-7}\).
En este programa usamos \(f(x)=x^2\) como función a integrar. Si quisiera integrar otra función en otro rango de valores, sólo tendría que cambiar la definición de la función por otra y modificar los límites del intervalo a integrar. El programa no requiere ninguna otra modificación. Sólo hay quecambiar la función y volver a compilarlo.
| Cantidad de intervalos | Resultado de la aproximación |
| de la integral | |
| 10 | 0.33500001696869747 |
| 100 | 0.33334997741140865 |
| 1,000 | 0.33333354714617608 |
| 10,000 | 0.33333330969899705 |
| 100,000 | 0.33333330800677946 |
| 1,000,000 | 0.33333333080419814 |
| 10,000,000 | 0.33333334502431866 |
| 100,000,000 | 0.33333332725965703 |
| 1,000,000,000 | 0.33333330505514902 |
El método de trapecios, no es el método más usado pero si es un método que tiene interés académico porque sirve para entender la complejidad del cálculo y determinar cotas teóricas al problema de aproximar la integral. Existen métodos muchos mejores que el de trapecios y modificaciones para hacerlo mucho más eficiente.
La función sentencia que vimos en la sección anterior es muy útil pero tiene sus limitaciones. La mayor de ellas es que sólo permite una fórmula matemática que se escriba en un solo renglón. Por ejemplo, una función a trozos no se podría programar como función sentencia, ya que se necesitarían al menos más de un renglón para programarla con el comando IF().
En cambio, la función externa, trabaja como si fuera otro programa y este es llamado por el programa principal al igual que la función sentencia. Un esquema simple, sería:
\(\ \ \ \ \ \ \ \)program Principal
\(\ \ \ \ \ \ \ \)real*4 F
\(\ \ \ \ \ \ \ \vdots\)
\(\ \ \ \ \ \ \ \)A= F(x)+5.0
\(\ \ \ \ \ \ \ \vdots\)
\(\ \ \ \ \ \ \ \)END
\(\ \ \ \ \ \ \ \)Function F(x)
\(\ \ \ \ \ \ \ \vdots\)
\(\ \ \ \ \ \ \ \)F = \(\cdots\)
\(\ \ \ \ \ \ \ \)RETURN
\(\ \ \ \ \ \ \ \)END
Como se ve en el esquema, la función fue escrita en el mismo archivo del programa, después de la sentencia END2. Notar que en general la función tiene la misma estructura que un programa salvo por el inicio como FUNCTION nombre(variables que recibe) y la orden RETURN que devuelve la ejecución hacia el programa principal.
Detalles que definen a la función externa:
Puede haber muchas funciones que son utilizadas por un programa.
Las funciones tienen variables internas que guardan datos que son de la función, cualquier modificación que se realice sobre ellas no afecta las variables del programa principal aunque tengan el mismo nombre), con la excepción del nombre de la función que retorna con el valor del resultado al programa principal).
La orden RETURN devuelve la ejecución al lugar exacto donde la función fue llamada. Un programa puede llamar repetidamente a la función desde distintos lugares.
Las funciones externas, al igual que la función sentencia son para el programa principal variables y cumplen con las reglas de estas. Según la primera letra de su nombre serán enteras o reales y se deberá definir cualquier otro tipo en forma explícita. Ejemplo: Si escribo Complex G(x,y) en el programa principal, en el comienzo de la función se deberá también escribir Complex Function G(x,y), para definirla como compleja.
La función cuando es llamada depende para realizar su cálculo de las variables de referencia que se encuentran dentro del paréntesis. A estas variables se las denomina argumentos de la función. Los nombres de estos argumentos en el programa que llama a la función, pueden o no coincidir con los que se usan en la función para hacer los cálculos. Es decir, pueden tener otros nombres, ya que en realidad se copian en el momento de la llamada a la función. Pero tienen que ser del mismo tipo de variable. Si el primer argumento es un entero, también debe ser un entero en la función, lo mismo para el segundo o tercero y así. Incluso pueden ser variables dimensionadas y en ese caso no sólo debe coincidir el tipo de variable sino la dimensión y debe repetirse la definición de dimensión en la función. Ejemplo:
\(\ \ \ \ \ \ \ \)programa principal
\(\ \ \ \ \ \ \ \)REAL*4 A(100)
\(\ \ \ \ \ \ \ \)Complex C
\(\ \ \ \ \ \ \ \vdots\)
\(\ \ \ \ \ \ \ \)Z = F(A,C)
\(\ \ \ \ \ \ \ \vdots\)
\(\ \ \ \ \ \ \ \)END
\(\ \ \ \ \ \ \ \)Function F(X2,B)
\(\ \ \ \ \ \ \ \)REAL*4 X2(100)
\(\ \ \ \ \ \ \ \)COMPLEX B
\(\ \ \ \ \ \ \ \vdots\)
\(\ \ \ \ \ \ \ \)RETURN
\(\ \ \ \ \ \ \ \)END
En este ejemplo, la función fue escrita de argumentos X2 y B, pero en el programa principal se la llama con las variables A y C. Donde A es vector de 100 elementos y C es un número complejo. Por eso X2 y B son definidas en la función del mismo tipo que las variables del programa principal. Note que el orden es quien decide cuál variable se copia a la correcta, en este caso la primera es el vector y la segunda es el complejo. El orden en el llamado determina cuál variable es cuál en la función.
Las funciones fueron inventadas para el Fortran y copiadas con modificaciones en todos los lenguajes más modernos. Se las suele llamar en distintas encarnaciones como procedimientos, subprogramas, etc. En el caso de Python, que veremos más adelante en la cursada, se viola la idea de función matemática ya que estas pueden devolver mas de un valor. Pero en sí, son muy similares a las de Fortran.
Por ejemplo, si programamos una función que nos calcule la serie \(\sum_{i=1}^n{1/i^k}\), donde básicamente le enviamos N, K y nos devuelve la suma de la serie, sería así:
\(\ \ \ \ \ \ \ \) Function Serie(n,k)
\(\ \ \ \ \ \ \ \) Serie=0.
\(\ \ \ \ \ \ \ \) DO i=1, n
\(\ \ \ \ \ \ \ \ \ \ \) x = i
\(\ \ \ \ \ \ \ \ \ \ \) Serie = Serie + 1/ x**k
\(\ \ \ \ \ \ \ \) ENDDO
\(\ \ \ \ \ \ \ \) RETURN
\(\ \ \ \ \ \ \ \) end