¿Como invocar una subrutina de Fortran desde C++?

En el presente trabajo se plasma una forma sencilla así como los detalles paso a paso de como invocar una subrutina de Fortran desde C++, lo cual sera de utilidad para cuando se quiera reutilizar código de Fortran y al mismo tiempo aprovechar las bondades de C++, lo anterior, debido a que Fortran cuenta con un numero valioso de funciones las cuales C++ no soporta y otros aspectos relacionados con la velocidad que Fortran puede llegar a alcanzar trabajando con tipos de datos complejos.

Introducción

A medida que la tecnología avanza, nuevos lenguajes de programación han aparecido en el mercado, se han popularizado los lenguajes de alto nivel debido a que las grandes empresas han hecho disponible una cantidad muy grande de librerías y frameworks para facilitar el desarrollo en aplicaciones de media complejidad en donde solo se requiere la manipulación de datos y reglas de negocio sencillas, este no ha sido el caso de las aplicaciones científicas y matemáticas debido a la gran demanda de recursos de procesamiento que éste tipo de aplicaciones requieren. Teniendo ésto en cuenta y pensando en las bondades que Fortran ofrece para aplicaciones de tipo científico, un gran número de personas entre científicos, investigadores, matemáticos, académicos entre otros, han adoptado el lenguaje de programación Fortran como su lenguaje primario para aplicaciones que resuelven problemas matemáticos complejos y que requieren una gran cantidad de procesamiento, muchas de estas aplicaciones se han hecho disponibles para resolver cuestiones complejas y han sido probadas a lo largo de los años por lo que se tiene la confianza de que son rutinas robustas y que proveen al usuario con los resultados deseados.

Es de ahí de donde surge la necesidad de buscar la manera de invocar rutinas de Fortran desde C++, C++ se ha convertido en un lenguaje de programación que puede combinar aspectos tanto de alto nivel como de bajo nivel, es un lenguaje muy flexible y poderoso el cual también soporta el paradigma de la programación orientada a objetos lo que lo hace más atractivo para desarrollos modernos.

Con la comunicación de los lenguajes C++ y Fortran, se logra proveer al usuario con los resultados y las capacidades de ambos mundos, sin contar con que se agiliza el tiempo de desarrollo ya que se vuelve innecesario reprogramar las subrutinas ya hechas en Fortran, se promueve la reutilización de código y se aprovecha mejor las inversiones de desarrollo que ya se hicieron en ese lenguaje.

Limitaciones

Hay cuestiones que se deben tener en cuenta al momento de tratar de invocar un lenguaje desde otro lenguaje, las grandes limitaciones que impiden que esto sea posible se enlistan a continuación:

  1. Manejo de Interrupciones
  2. Manejo de Entradas / Salidas
  3. Manejo de memoria asignada dinámicamente

Estas cuestiones solo las podrá contener uno de los dos programas que se van a enlazar debido a la naturaleza de ellas, pero afortunadamente la mayoría de las subrutinas o librerías disponibles en el mercado cuentan con un muy buen diseño por lo que los puntos anteriores no son un problema, un gran numero de rutinas numéricas y de resolución de ecuaciones integran un buen diseño en sus estructuras lo que las hace altamente reutilizables tanto desde un mismo lenguaje como desde lenguajes externos.

Algunas Diferencias entre C++ y Fortran

A continuación se muestran algunas diferencias entre lenguajes C++ y Fortran así como también la manera correcta de manejar estas diferencias para poder tener éxito invocando un lenguaje desde otro.

  • Parámetros: Una de las grandes diferencias a tener en cuenta cuando se quiere construir una interfaz entre C++ y Fortran es que C++ pasa los parámetros comunes por valor en donde Fortran pasa dichos parámetros por referencia, esto significa que al momento de definir las subrutinas que serán llamadas desde C++ habrá que definir y pasarle a las rutinas de Fortran los parámetros por Referencia para que éstas puedan funcionar, pasar un parámetro por referencia significa pasar la dirección de memoria donde este parámetro se encuentra localizado en vez del valor del parámetro como C++ lo hace por defecto.
  • Nombre de la Subrutina: Otro de los aspectos a tener en cuenta en la construcción de la interfaz C++-Fortran son los nombres de las subrutinas, una de las cuestiones que el compilador de fortran realiza al momento de generar los objetos a partir del código fuente es que agrega un guión bajo a todas las subrutinas, esto se tiene que tener presente ya que al momento de definir la subrutina de Fortran que será invocada se tendrá que concatenar un guión bajo a la subrutina ya que de otra forma C++ no será capaz de llamar la subrutina ya que en el proceso de compilación ésta cambio de nombre.
  • Tipo de Datos: Los tipos de datos en C++ y Fortran NO son similares, por lo que se tendrá que encontrar el tipo de datos equivalente de C++ cuando se llama una subrutina de Fortran, ésto se refiere a que si los tipos de datos no concuerdan en capacidad habrá errores al momento de hacer el llamado desde C++ a Fortran, en la siguiente sección se incluye una tabla de equivalencias de tipos de datos entre C++ y Fortran ésta será sumamente necesaria para definir las subrutinas Fortran que serán invocadas.
  • Orden en Arreglos: Es importante notar que el orden en que se almacenan los valores en los arreglos cambia de acuerdo al lenguaje que se este utilizando, pero esto se puede manejar de manera sencilla cambiando en orden de las posiciones del arreglo, es decir Arr(i,j) en Fortran seria en C++ algo similar a Arr[j][i].
  • Índice en Arreglos: Es importante recordar que los índices de los arreglos y matrices en Fortran empiezan con el uno a diferencia de C++ el cual empieza con el cero, es decir, que si en Fortran se define una matriz de una longitud de n elementos entonces los índices de dicha matriz irán de 1 a n a diferencia de C++ en donde los índices serian de 0 a n-1.

Equivalencia de Tipos entre C++ y Fortran

Fortran C++
byte unsigned char
integer*2 short int
integer long int or int
integer iabc(2,3) int iabc[3][2]
logical long int or int
logical*1 bool (C++, One byte)
real float
real*8 double
real*16 long double
complex struct{float realnum; float imagnum;}
double complex struct{double dr; double di;}
character*6 abc char abc[6]
character*6 abc(4) char abc[4][6]
parameter #define PARAMETER value

extern

extern es una palabra reservada de C++ la cual nos permite hacer uso de rutinas definidas en otros lenguajes, el requerimiento mas importante para a utilización de rutinas escritas en diferentes lenguajes es que se defina la función que se va a utilizar con la palabra reservada “extern”, de esta forma el compilador (conoce) el tipo de datos que va a enviar a la función externa.

Ejemplo, C++ llama a Fortran

El ejemplo sencillo que se expondrá a continuación consiste en llamar a una rutina bien conocida de Fortran la cual se encarga de calcular los eigenvalores de una matriz dada, lo interesante de este ejemplo es que se estará llamando a dicha rutina desde un programa hecho en C++ y se mostrará la manera de hacer el enlace entre los dos lenguajes, a continuación se enlista la definición del encabezado para la subrutina de Fortran.

eigen_f.f

subroutine cg(nm,n,ar,ai,wr,wi,matz,zr,zi,fv1,fv2,fv3,ierr)
c
      integer n,nm,is1,is2,ierr,matz
      double precision ar(nm,n),ai(nm,n),wr(n),wi(n),zr(nm,n),zi(nm,n),
     x       fv1(n),fv2(n),fv3(n)
c
c     this subroutine calls the recommended sequence of
c     subroutines from the eigensystem subroutine package (eispack)
c     to find the eigenvalues and eigenvectors (if desired)
c     of a complex general matrix.
 
c
c ... omitido por simplicidad
c

Una vez que se tiene la subrutina a llamar de fortran y de acuerdo a la tabla de equivalencias que se definió en la parte de arriba, se utiliza la palabra clave extern para declarar esta función desde dentro de la rutina C++ como se muestra a continuación.

eigen_c_cgi.cpp

//  Interfaces to FORTRAN77 routines.
//  The names now have underscores appended, and
//  scalar parameters are passed by reference.
//  The EXTERN statement used here stops C++ from "name mangling", which
//  would have destroyed the correspondence between the symbolic names
//  of the subroutines as recorded in the C++ and FORTRAN77 compiled codes.
//
extern "C"; {
    void cg_ (int *nr, int *nc, double ar[CONST_ROWS][CONST_COLS], double ai[CONST_ROWS][CONST_COLS], double wr[],
        double wi[], int *flag, double zr[CONST_ROWS][CONST_COLS], double zi[CONST_ROWS][CONST_COLS], double fv1[],
        double fv2[], double fv3[], int *ierr);
}

Utilizando la tabla de equivalencia de tipos se determinan los tipos de cada parámetro que la subrutina de Fortran esta esperando a su similar en C++ y tomando en cuenta las reglas para poder hacer el llamado se define el tipo de datos y en caso de ser escalar se define que se pasara por referencia y no por valor ya que el comportamiento de fortran es manejar referencias en vez de valores, también, nótese como se le ha agregado un guión bajo al nombre de la subrutina de fortran en la definición extern, esto debido a que el compilador Fortran como se ha mencionado anteriormente al momento de compilar y generar el objeto le agrega de forma automática este tipo de prefijo al nombre de la subrutina por lo que es necesario para que el compilador C++ pueda comunicarse con Fortran.

A continuación se muestra como se construye una matriz y se pasa a fortran tomando en cuenta los índices así como el orden en que se pasa la matriz, se muestra como enviar parámetros escalares por referencia, y la manera de hacer el llamado a nuestra función extern para obtener un resultado.

eigen_c_cgi.cpp

int main() {
    cout << "content-type: text/html" << endl << endl;
    cout << "<html><body>" << endl;
    cout << "<h1>*** Program that calls a Fortran subroutine ***</h1>" << endl << endl;
 
    int ierr, flag, i, j;
    flag = 0; // no need extra results from [cg_] fortran subroutine
 
    double ar[ROWS][COLS], ai[ROWS][COLS], zr[ROWS][COLS], zi[ROWS][COLS];
    double wr[ROWS], wi[ROWS];
    double fv1[ROWS], fv2[ROWS], fv3[ROWS];
 
    for (i = 0; i < ROWS; i++) {
        for (j = 0; j < COLS; j++) {
            ar[j][i] = (i + 1) + (j + 1);  // (+1) this is because of fortran indexes changes
            ai[j][i] = 0.0;
            zr[j][i] = 0.0;
            zi[j][i] = 0.0;
        }
    }
 
    // print matrix
    cout << "+ Matrix to pass to Fortran" << "<br />";
    cout << "<p>";
    for (i = 0; i < ROWS; i++) {
        for (j = 0; j < COLS; j++) {
            cout << ar[i][j] << " && ";
        }
        cout << "<br />";
    }
    cout << "</p>";
 
    cout << "EIGEN BlackBox:" << "<br />";
    cout << "  Combination C++ and FORTRAN77 version." << "<br />";
    cout << "  Demonstrate how a C++ main program can call" << "<br />";
    cout << "  a FORTRAN77 library." << "<br />";
 
    int rows, cols;  // we have to pass the COLS and ROWS by reference to fortran
    rows = ROWS;
    cols = COLS;
 
    cg_ (&rows, &cols, ar, ai, wr, wi, &flag, zr, zi, fv1, fv2, fv3, &ierr);
 
    // results
    cout << "<br />";
    cout << "Results: " << "<br />" << wr[0] << "<br >" <<  wr[1] << "<br />" << wr[2] << "<br />" << wr[3] << "<br />";
 
    cout << "<br />";
    cout << "EIGEN BlackBox: " << "<br />";
    cout << "  Normal end of execution. " << "<br />";
    cout << "<br />";
 
    cout << "</body></html>" << "<br />";
    return 0;
}

Como se puede observar en el ejemplo, los resultados calculados de la función son accesibles a través de el vector wr, los cuales son mostrados en pantalla al finalizar la ejecución del programa, en el siguiente listado se muestran los comandos que se tienen que ejecutar para compilar y hacer el enlace de los programas C++ y Fortran y la manera de ejecutar la solución final.

eigen.sh

#!/bin/bash
#
#  Compile the FORTRAN90 library.
#
gfortran -c eigen_f.f
if [ $? -ne 0 ]; then
  echo "Errors executing gfortran -c eigen_f.f"
  exit
fi
#
#  Compile the C++ main program.
#
g++ -c eigen_c_cgi.cpp
if [ $? -ne 0 ]; then
  echo "Errors executing g++ -c eigen_c.C"
  exit
fi
#
#  Link the main program and library.
#  OR, you can use the G++ command to link,
#  if you include the -lgfortran library,
#
g++ eigen_c_cgi.o eigen_f.o -lgfortran
if [ $? -ne 0 ]; then
  echo "Errors executing gcc -c eigen_c.c"
  exit
fi
#
#  Run the program.
#
mv a.out eigen_c_cgi.cgi
#./eigen_c_cgi > eigen_c_cgi_output.txt
#
#  Clean up.
#
rm eigen_f.o
rm eigen_c_cgi.o
rm eigen_c_cgi
echo "Test output written to eigen_c_cgi_output.txt"
#
#  Terminate.
#
exit

Como se puede observar en el script anterior, el primer paso a dar sera el de compilar la librería Fortran a la que se quiere acceder, a continuación se debe compilar el programa principal el cual llamara a su vez a la rutina Fortran para finalmente hacer el enlace entre los programas compilados de de Fortran y C++ utilizando para esto el compilador g++, una vez que se tiene un resultado del enlace anterior este se re nombra y ejecuta y los resultados de la ejecución son enviados a un archivo de texto para mayor comodidad, por último se borran los objetos generados en el proceso se imprime una etiqueta final y el script queda listo para otra ejecución si fuese necesaria.

Para compilar, linkear y ejecutar el programa solo se deben ejecutar el script bash con los siguientes comandos, (el primero le da permiso de ejecucion al archivo).

chmod +x eigenfunc.sh
sh eigenfunc.sh

Y a continuación se enlistan los resultados de la ejecución.

Started ajax request..
Before send..

+ Matrix to pass to Fortran
2   3   4   5
3   4   5   6
4   5   6   7
5   6   7   8

EIGEN BlackBox:
Combination C++ and FORTRAN77 version.
Demonstrate how a C++ main program can call
a FORTRAN77 library.

Matrix Eigenvalues:
20.9545
-0.954451
-8.14465e-17
-8.14465e-17

Matrix Reduced Frequency Eigenvalues:
7.28546
nan
nan
nan

EIGEN BlackBox:
Normal end of execution.

Completed..
With success..

Conclusiones

En esta sección se ha presentado una forma sencilla de hacer una relacion entre C++ y Fortran para hacer posible el compartir lógica entre ambos lenguajes, con esto, se ha logrado el tener una alternativa para cuando se quieren aprovechar rutinas tanto matemáticas como científicas así como librerías desarrolladas por terceros en un lenguaje y utilizarlas desde otro.

Cabe mencionar que este procedimiento es similar y aplicable a otros lenguajes con pocas modificaciones, lo que tal vez se exponga en un apartado futuro, también, cabe mencionar que en futuros apartados se describirá cuales son las ventajas de utilizar Fortran frente a C++ en cierto tipo de desarrollos y aprovechar esas bondades por medio del enlace de estos lenguajes.

A continuación se anexa el programa online de este artículo:
[pageview url=”http://phoxonics.dyndns.org:5080/EigenWeb.html” width=”100%” height=”100%” scrolling=”auto”]

Leave a Reply

Your email address will not be published. Required fields are marked *