[ anterior ] [ Contenidos ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 10 ] [ 11 ] [ 12 ] [ 13 ] [ siguiente ]
Fortran 90
para la asignatura Química Computacional
Los objetivos de esta clase son los siguientes:
Presentar los módulos y las ventajas que aportan.
Uso de módulos para la definición de variables. Reemplazo de bloques COMMON.
Uso de módulos para la definición de funciones y subrutinas.
Definición de variables públicas y privadas en módulos. Visibilidad en el módulo.
La definición de módulos permite escribir código de forma más clara y flexible. En un módulo podemos encontrar
Declaración global de variables.
Reemplazan a las órdenes COMMON e INCLUDE de
FORTRAN 77
.
Declaración de bloques INTERFACE.
Declaración de funciones y subrutinas. La declaración de funciones y subrutinas en un módulo es conveniente para evitar la inclusión de los correspondientes INTERFACE, ya que estos están ya implícitos en el módulo.
Control del acceso a los objetos, lo que permite que ciertos objetos tengan carácter público y otros privado.
Los módulos permiten empaquetar tipos derivados, funciones, subrutinas para
proveer de capacidades de programación orientada a objetos. Pueden también
usarse para definir extensiones semánticas al lenguaje FORTRAN
.
La sintaxis para la declaración de un módulo es la siguiente:
MODULE module name IMPLICIT NONE SAVE declaraciones y especificaciones [ CONTAINS definición de subrutinas y funciones ] END MODULE module name
La carga del módulo se hace mediante la orden USE MODULE module name que debe preceder al resto de órdenes de la unidad de programa en el que se incluya. Desde un módulo puede llamarse a otro módulo. A continuación desarrollamos brevemente estas ideas.
Una de las funciones de los módulos es permitir el intercambio de variables entre diferentes programas y subrutinas sin recurrir a los argumentos. La otra función principal es, haciendo uso de CONTAINS, definir funciones, subrutines y bloques INTERFACE.
La inclusión de estas unidades en un módulo hace que todos los detalles acerca de las subrutinas y funciones implicadas sean conocidas para el compilador lo que permite una más rápida detección de errores. Cuando una subrutina o una función se compila en un módulo y se hace accesible mediante USE MODULE se dice que tiene una interfaz explícita (explicit interface), mientras que en caso contrario se dice que tiene una interfaz implícita (implicit interface).
La definición de módulos favorece la llamada encapsulación, que consiste en definir secciones de código que resultan fácilmente aplicables en diferentes situaciones. En esto consiste la base de la llamada programación orientada a objetos. En el Programa ejemplo_10_1.f90, Sección 10.3.1 presentamos como se define un módulo (usando la orden MODULE en vez de PROGRAM para la definición de un stack de enteros. Es importante tener en cuenta como se definen en el módulo las variables STACK_POS y STORE con el atributo SAVE, para que su valor se conserve entre llamadas. Esto es especialmente importante cuando el módulo se llama desde una subrutina o función en vez de desde el programa principal.
Este módulo puede ser accedido por otra unidad de programa que lo cargue usando la orden USE. Debe compilarse previamente a la unidad de programa que lo cargue.
PROGRAM Uso_Stack ! USE Stack ! CARGA EL MODULO ! IMPLICIT NONE .... .... CALL POP(23); CAL PUSH(20) .... .... END PROGRAM Uso_Stack
Como vemos en el Programa ejemplo_10_1.f90, Sección 10.3.1 las variables dentro de un módulo pueden definirse como variables privadas, con el atributo PRIVATE. Esto permite que no se pueda acceder a estas variables desde el código que usa el módulo. El programa que carga el módulo solo puede acceder a las subrutinas POP y PUSH. La visibilidad por defecto al definir una variable o procedimiento en un módulo es PUBLIC. Es posible añadir el atributo a la definición de las variables
INTEGER, PRIVATE, PARAMETER :: STACK_SIZE = 500 INTEGER, PRIVATE, SAVE :: STORE(STACK_SIZE) = 0, STACK_POS = 0
En ocasiones es posible que variables o procedimientos definidos en un módulo entren en conflicto con variables del programa que usa el módulo. Para evitar esto existe la posibilidad de renombrar las variables que carga el módulo, aunque esto solo debe hacerse cuando sea estrictamente necesario.
Si, por ejemplo, llamamos al módulo Stack desde un programa que ya tiene una variable llamada PUSH podemos renombrar el objeto PUSH del módulo a STACK_PUSH al invocar el módulo
USE Stack, STACK_PUSH => PUSH
Se pueden renombrar varios objetos, separándolos por comas.
Es posible hacer que solo algunos elementos del módulo sean accesibles desde el programa que lo invoca con la cláusula ONLY, donde también es posible renombrar los objetos si es necesario. Por ejemplo, con la llamada
USE Stack, ONLY: POP, STACK_PUSH => PUSH
Solamente se accede a POP y PUSH, y este último se renombra a STACK_PUSH.
Para definir variables comunes a diferentes partes de un programa se debe evitar el uso de variables en COMMON y, en vez de ello, se siguen los pasos siguientes.
Declarar las variables necesarias en un MODULE.
Otorgar a estas variables el atributo SAVE.
Cargar este módulo (USE module_name) desde aquellas unidades que necesiten acceso a estos datos globales.
Por ejemplo, si existen una serie de constantes físicas que utilizaremos en varios programas podemos definirlas en un módulo:
MODULE PHYS_CONST ! IMPLICIT NONE ! SAVE ! REAL, PARAMETER :: Light_Speed = 2.99792458E08 ! m/s REAL, PARAMETER :: Newton_Ctnt = 6.67428E-11 ! m3 kg-1 s-2 REAL, PARAMETER :: Planck_Ctnt = 4.13566733E-15 ! eV s ! REAL :: Otra_variable ! END MODULE PHYS_CONST
En este módulo se definen tres constantes físicas (con el atributo PARAMETER, ya que son constantes) y una cuarta variable a la que se desea acceder que no permanece constante. En cualquier programa, función o subrutina que quieran usarse estas variables basta con cargar el módulo
PROGRAM CALCULUS ! USE PHYS_CONST ! IMPLICIT NONE ! REAL DISTANCE, TIME ! ... DISTANCE = Light_Speed*TIME ... ! END PROGRAM CALCULUS
El Programa ejemplo_10_2.f90, Sección 10.3.2 es un programa simple donde se utiliza el módulo para el manejo de un stack presentado para realizar operaciones (adición y substracción) con enteros en notación polaca inversa (RPN, reverse Polish notation).
Esta notación permite no usar paréntesis en las operaciones algebraicas y resulta más rápida que la notación usual. Si, por ejemplo, en el stack existen los números (23, 10, 33) y tenemos en cuenta que un stack se rige por el principio last in, first out, tendremos que si introducimos un número más (p.e. 5) y realizamos las operaciones de suma (plus) y substracción (minus) tendremos lo siguiente
- - - - - 23 - - 23 10 23 - 10 33 10 23 33 -> 5 -> 38 (=33+5) -> -28 (=10-38) 5 plus minus
Para llevar a cabo esta tarea se carga el módulo Stack en (1). Una vez cargado el módulo podemos acceder a las subrutinas POP y PUSH que nos permiten manejar el stack. En (2) comienza el bucle principal, con la etiqueta inloop, que termina cuando el usuario da como input Q, q o quit.
Para controlar este bucle se utiliza una estructura SELECT CASE que comienza en (3). Esta estructura analiza cuatro casos posibles:
(4): salir del programa
(5): suma
(6): resta
(7): introduce número en el stack (DEFAULT)
En el último caso se transforma la variable de carácter leída en una variable entera para almacenarla en el stack.
Para compilar y correr este programa podemos hacerlo compilando previamente el
módulo, si lo hemos salvado en el fichero ejemplo_10_1_Stack.f90
$ gfortran -c ejemplo_10_1_Stack.f90 $ gfortran -o ejemplo_10_2 ejemplo_10_2.f90 ejemplo_10_1_Stack.o
El uso de módulos también permite, de forma flexible, segura y fácil de modificar, controlar la precisión de los números reales (o enteros) en los cálculos que se lleven a cabo. Una posible forma de definir de forma portable la doble precisión es mediante un sencillo módulo, llamado dble_prec. Como vimos en el programa Programa ejemplo_2_6.f90, Sección 2.3.6 los números reales de doble precisión tienen un KIND = 8. Para hacer el código independiente de la plataforma donde compilemos podemos hacer
MODULE dble_prec IMPLICIT NONE INTEGER, PARAMETER :: dbl = KIND(1.0D0) END MODULE dble_prec
Por tanto podemos definir esa precisión cargando este módulo, p.e.
PROGRAM TEST_MINUIT ! USE dble_prec ! IMPLICIT NONE ! ! Variable Definition REAL(KIND=dbl), PARAMETER :: PI = 4.0_dbl*ATAN(1.0_dbl) REAL(KIND=dbl) :: ENERF .... ....
Esto favorece la portabilidad y reduce el riesgo de errores ya que para cambiar
la precisión con la que se trabaja solamente es necesario editar el módulo. En
el Programa ejemplo_10_3.f90, Sección 10.3.3
introducimos esta mejora en el programa Programa ejemplo_9_6.f90, Sección 9.3.6.
Se almacena el módulo simple anteriormente descrito en un fichero llamado,
p.e., dble_prec.f90
y se compila previamente:
$ gfortran -c dble_prec.f90 $ gfortran -o ejemplo_10_3 ejemplo_10_3.f90 dble_prec.o
Un módulo más completo, donde se definen diferentes tipos de enteros y de reales es el dado en el programa Programa ejemplo_10_4.f90, Sección 10.3.4.
En un ejercicio se plantean al alumnos diferentes maneras de mejorar el programa simple Programa ejemplo_10_2.f90, Sección 10.3.2.
MODULE Stack ! ! MODULE THAT DEFINES A BASIC STACK ! IMPLICIT NONE ! SAVE ! INTEGER, PARAMETER :: STACK_SIZE = 500 INTEGER :: STORE(STACK_SIZE) = 0, STACK_POS = 0 ! PRIVATE :: STORE, STACK_POS, STACK_SIZE PUBLIC :: POP, PUSH ! CONTAINS ! SUBROUTINE PUSH(I) ! INTEGER, INTENT(IN) :: I ! IF (STACK_POS < STACK_SIZE) THEN ! STACK_POS = STACK_POS + 1; STORE(STACK_POS) = I ! ELSE ! STOP "FULL STACK ERROR" ! ENDIF ! END SUBROUTINE PUSH !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! SUBROUTINE POP(I) ! INTEGER, INTENT(OUT) :: I ! IF (STACK_POS > 0) THEN ! I = STORE(STACK_POS); STACK_POS = STACK_POS - 1 ! ELSE ! STOP "EMPTY STACK ERROR" ! ENDIF ! END SUBROUTINE POP ! END MODULE Stack
PROGRAM RPN_CALC ! ! SIMPLE INTEGER RPN CALCULATOR (ONLY SUM AND SUBSTRACT) ! USE Stack !! (1) ! IMPLICIT NONE ! INTEGER :: KEYB_DATA CHARACTER(LEN=10) :: INPDAT ! INTEGER :: I, J, K, DATL, NUM, RES ! ! inloop: DO !! MAIN LOOP (2) ! READ 100, INPDAT ! SELECT CASE (INPDAT) !! (3) ! CASE ('Q','q') !! EXIT (4) PRINT*, "End of program" EXIT inloop CASE ('plus','Plus','PLUS','+') !! SUM (5) CALL POP(J) CALL POP(K) RES = K + J PRINT 120, K, J, RES CALL PUSH(RES) CASE ('minus','Minus','MINUS','-') !! SUBSTRACT (6) CALL POP(J) CALL POP(K) RES = K - J PRINT 130, K, J, RES CALL PUSH(RES) CASE DEFAULT !! NUMBER TO STACK (7) ! DATL = LEN_TRIM(INPDAT) ! RES = 0 DO I = DATL, 1, -1 NUM = IACHAR(INPDAT(I:I)) - 48 RES = RES + NUM*10**(DATL-I) ENDDO ! PRINT 110, RES CALL PUSH(RES) END SELECT ! ENDDO inloop ! 100 FORMAT(A10) 110 FORMAT(1X, I10) 120 FORMAT(1X, I10,' + ', I10,' = ', I20) 130 FORMAT(1X, I10,' - ', I10,' = ', I20) END PROGRAM RPN_CALC
PROGRAM ejemplo_10_3 ! USE dble_prec ! IMPLICIT NONE ! INTEGER :: I, IERR REAL(KIND=dbl), DIMENSION(:), ALLOCATABLE :: X, Y REAL(KIND=dbl) :: M, SD, MEDIAN ! interface block INTERFACE SUBROUTINE STATS(VECTOR,N,MEAN,STD_DEV,MEDIAN) ! USE dble_prec ! IMPLICIT NONE INTEGER , INTENT(IN) :: N REAL(KIND=dbl) , INTENT(IN) , DIMENSION(:) :: VECTOR REAL(KIND=dbl) , INTENT(OUT) :: MEAN REAL(KIND=dbl) , INTENT(OUT) :: STD_DEV REAL(KIND=dbl) , INTENT(OUT) :: MEDIAN END SUBROUTINE STATS END INTERFACE ! READ*, I ! ALLOCATE(X(1:I), STAT = IERR) IF (IERR /= 0) THEN PRINT*, "X allocation request denied." STOP ENDIF ! ALLOCATE(Y(1:I), STAT = IERR) IF (IERR /= 0) THEN PRINT*, "Y allocation request denied." STOP ENDIF ! CALL BOX_MULLER(I) ! PRINT*, X CALL STATS(X,I,M,SD,MEDIAN) ! PRINT *,' MEAN = ',M PRINT *,' STANDARD DEVIATION = ',SD PRINT *,' MEDIAN IS = ',MEDIAN ! IF (ALLOCATED(X)) DEALLOCATE(X, STAT = IERR) IF (IERR /= 0) THEN PRINT*, "X NON DEALLOCATED!" STOP ENDIF PRINT*, Y CALL STATS(Y,I,M,SD,MEDIAN) ! PRINT *,' MEAN = ',M PRINT *,' STANDARD DEVIATION = ',SD PRINT *,' MEDIAN IS = ',MEDIAN ! IF (ALLOCATED(Y)) DEALLOCATE(Y, STAT = IERR) IF (IERR /= 0) THEN PRINT*, "Y NON DEALLOCATED!" STOP ENDIF ! CONTAINS ! SUBROUTINE BOX_MULLER(dim) ! ! Uses the Box-Muller method to create two normally distributed vectors ! INTEGER, INTENT(IN) :: dim ! REAL(KIND=dbl), PARAMETER :: PI = ACOS(-1.0_dbl) REAL(KIND=dbl), DIMENSION(dim) :: RANDOM_u, RANDOM_v ! Automatic arrays ! CALL RANDOM_NUMBER(RANDOM_u) CALL RANDOM_NUMBER(RANDOM_v) ! X = SQRT(-2.0_dbl*LOG(RANDOM_u)) Y = X*SIN(2.0_dbl*PI*RANDOM_v) X = X*COS(2.0_dbl*PI*RANDOM_v) ! END SUBROUTINE BOX_MULLER ! END PROGRAM ejemplo_10_3 SUBROUTINE STATS(VECTOR,N,MEAN,STD_DEV,MEDIAN) USE dble_prec IMPLICIT NONE ! Defincion de variables INTEGER , INTENT(IN) :: N REAL(KIND=dbl) , INTENT(IN) , DIMENSION(:) :: VECTOR !! (1) REAL(KIND=dbl) , INTENT(OUT) :: MEAN REAL(KIND=dbl) , INTENT(OUT) :: STD_DEV REAL(KIND=dbl) , INTENT(OUT) :: MEDIAN REAL(KIND=dbl) , DIMENSION(1:N) :: Y REAL(KIND=dbl) :: VARIANCE = 0.0_dbl REAL(KIND=dbl) :: SUMXI = 0.0_dbl, SUMXI2 = 0.0_dbl ! SUMXI=SUM(VECTOR) !! (6) SUMXI2=SUM(VECTOR*VECTOR) !! (6) MEAN=SUMXI/N VARIANCE=(SUMXI2-SUMXI*SUMXI/N)/(N-1) STD_DEV = SQRT(VARIANCE) Y=VECTOR ! Ordena valores por proceso de seleccion CALL SELECTION IF (MOD(N,2) == 0) THEN MEDIAN=(Y(N/2)+Y((N/2)+1))/2 ELSE MEDIAN=Y((N/2)+1) ENDIF CONTAINS !! (7) SUBROUTINE SELECTION IMPLICIT NONE INTEGER :: I,J,K REAL :: MINIMUM DO I=1,N-1 K=I MINIMUM=Y(I) DO J=I+1,N IF (Y(J) < MINIMUM) THEN K=J MINIMUM=Y(K) END IF END DO Y(K)=Y(I) Y(I)=MINIMUM END DO END SUBROUTINE SELECTION END SUBROUTINE STATS
MODULE NUMERIC_KINDS ! 4, 2, AND 1 BYTE INTEGERS INTEGER, PARAMETER :: & i4b = SELECTED_INT_KIND(9), & i2b = SELECTED_INT_KIND(4), & i1b = SELECTED_INT_KIND(2) ! SINGLE, DOUBLE, AND QUADRUPLE PRECISION INTEGER, PARAMETER :: & sp = KIND(1.0), & dp = SELECTED_REAL_KIND(2*PRECISION(1.0_sp)), & qp = SELECTED_REAL_KIND(2*PRECISION(1.0_dp)) END MODULE NUMERIC_KINDS
[ anterior ] [ Contenidos ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 10 ] [ 11 ] [ 12 ] [ 13 ] [ siguiente ]
Lecciones de Fortran 90
para la asignatura Química Computacional
mailto:francisco.perez@dfaie.uhu.es