[ 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
Capítulo 10 - Subprogramas (III): módulos


10.1 Objetivos

Los objetivos de esta clase son los siguientes:

  1. Presentar los módulos y las ventajas que aportan.

  1. Uso de módulos para la definición de variables. Reemplazo de bloques COMMON.

  1. Uso de módulos para la definición de funciones y subrutinas.

  1. Definición de variables públicas y privadas en módulos. Visibilidad en el módulo.


10.2 Puntos destacables.

  1. La definición de módulos permite escribir código de forma más clara y flexible. En un módulo podemos encontrar

    1. Declaración global de variables.

      Reemplazan a las órdenes COMMON e INCLUDE de FORTRAN 77.

    1. Declaración de bloques INTERFACE.

    1. 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.

    1. Control del acceso a los objetos, lo que permite que ciertos objetos tengan carácter público y otros privado.

    1. 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.

  1. 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).

  1. 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.

  1. 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
    
  1. 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
    
  1. 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.

  1. 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.

  1. 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.

    1. Declarar las variables necesarias en un MODULE.

    1. Otorgar a estas variables el atributo SAVE.

    1. 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
    
  1. 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:

    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
    
  1. 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.


10.3 Programas usados como ejemplo.


10.3.1 Programa ejemplo_10_1.f90

     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

10.3.2 Programa ejemplo_10_2.f90

     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

10.3.3 Programa ejemplo_10_3.f90

     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

10.3.4 Programa ejemplo_10_4.f90

     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

$Id: clases_fortran.sgml,v 1.24 2013/07/02 09:38:58 curro Exp curro $

Curro Pérez Bernal mailto:francisco.perez@dfaie.uhu.es