Calling Fortran from Julia (ii)

Now to call savgol() from Julia requires some information, namely what the interface to the subroutine looks like. The Fortran 90 code for the subroutine header looks something like this:

subroutine savgol(c,np,nl,nr,ld,m)
integer ld,m,nl,np,nr
real c(np)

It specifies 6 parameters, the first of which is an array of real numbers, and the remaining five of which are integer‘s. The variable c returns an array of coefficients (to be used for further processing). So the trick is to associate the parameters in savgol() with their associated input values in ccall(). This is what the call the ccall() looks like:

ccall((:savgol_, "SG.so"), Int32, (Ptr{Cfloat},Ptr{Cint},
       Ptr{Cint},Ptr{Cint},Ptr{Cint},Ptr{Cint}), 
       c, &d, &nl, &nr, &0, &m)

Recall that a call to ccall() has four arguments:

  1. A “function” – library pair. This is savgol_ and “SG.so”.
  2. A return typeInt32 in this case, but the subroutine does not return anything.
  3. A tuple of input values. In this case Ptr{Float}, etc.
  4. The arguments actually passed to savgol().

Note that unlike C, all Fortran arguments must be passed by reference, hence the use of Ptr{} on all the arguments. The tricky part was again linking the datatypes, but this really just means tracing the required Julia type which can be found here. The Fortran integer type meshes well with Cint, and the Fortran real type associates with Julia’s Cfloat.

There is a cornucopia of existing libraries out there in Fortran, especially for math… why not use them?

Calling Fortran from Julia (i)

We have covered calling C from Julia, and calling Fortran is not that different.  Now it is not that uncommon for glue languages such as Python and Julia to be able to access existing code from other languages. It makes sense from the viewpoint of not having to actually re-engineer code that is known to work. However in Python you have to use something like f2py, the Fortran to Python interface generator. Or make the Fortran code callable from C and then bind that with Cython (see link). Seems like a pain right?

Julia makes this way easier, with no “wrappers” or anything similar required. Works in the same way as calling C code except for a couple of differences. So imagine a piece of Fortran code (contained in SG.f90) with three subroutines:

  • savgol() – performs Savitzky-Golay smoothing on 1D data.
  • ludcmp() – performs LU decomposition (called by savgol())
  • lubksb() – solves a series of linear equations (called by savgol())

Now we want to create a piece of Julia code which calls savgol(), without the need to translate the code to Julia. The first thing that needs to happen of course is that the Fortran code needs to be compiled into an object file.

gfortran -shared -fPIC SG.f90 -o SG.so

This creates the object file SG.so. However if you try and use the function savgol in ccall, you will likely get an error of the form:

ERROR: ccall: could not find function savgol in library

This is because Fortran tends to mangle, or disfigure names. This has to do with case insensitivity in Fortran, and is only problematic in the sense that every compiler does things a little differently. In the case of GNU Fortran,  it converts all characters to lowercase, and adds an underscore. If you aren’t sure, you can always check using the utility nm, which checks the symbol table (or name list) of an object file. So if it is applied to SG.so, we obtain:

>nm SG.so
 U ___powidf2
 U __gfortran_st_write
 U __gfortran_st_write_done
 U __gfortran_transfer_array_write
 U __gfortran_transfer_character_write
0000000000000ecd T _lubksb_
00000000000011cf T _ludcmp_
000000000000189d T _savgol_
 U dyld_stub_binder

You can see savgol has an underscore prepended to it as well, this can be ignored. Now it is possible to define the call to ccall() (covered in the next post).

 

 

Re-engineering versus Refactoring

When dealing with legacy software, it is important to understand what can be done with the software. Legacy software often consists of software that has been left to run for a long time without too many inherent changes, the “don’t fix what isn’t broken” strategy. As compilers in languages such as Fortran are backwards compatible, it is often possible to compile and run these old programs. Yet at some point it becomes necessary to deal with the old code. So how to is this achieved? Is the code to be re-engineered or refactored?

Re-engineering means making fundamental changes to the code. Here are three core methods of reengineering:

  1. Porting – programs are modified to work on a new hardware platform.
  2. Translation – programs are translated from legacy language to contemporary one.
  3. Migration – programs are converted from a legacy language to a newer dialect.

In essence this is no different to the work that would be done to an old building. It might be moved in its entirety to a new location, it might be completely rebuilt, or it might be made new, incorporating only the facade of the original building.

Refactoring on the other hand, leaves things more intact. Refactoring involves changing a piece of software in such a manner that the external behaviour of the code remains unchanged, but it’s internal structure and architecture are enhanced. This is akin to modernizing the plumbing and electrical system of an old building. It still functions and looks the same way, but the infrastructure has been improved. Refactoring takes control of decaying code, improving the readability and maintainability of existing code. Refactoring is done to fix short-cuts, eliminate duplication and dead code, and to make the design and logic clear. To make better and clearer use of the programming language. It does not necessarily imply that the code is migrated to a new dialect of the language. Refactoring is often a part of the life-cycle of software, and may not be targeted specifically at legacy code.

Reengineering and refactoring look very similar, and there are likely areas, such as migration, where they overlap. In reality the process of dealing with legacy code often begins with refactoring, and progresses to reengineering. In situations where the code base is too complex, it might be worthwhile trying to improve efficiency first by improving algorithms. If this doesn’t work however, reengineering might be in the cards.

Here’s an example of the possibilities when dealing with a legacy, say Fortran IV, piece of code. The refactoring may involve processes such as:

  1. eliminating equivalence statements: specifies that two or more variables or arrays in a program unit share the same memory.
  2. elimination of common blocks: shared, common storage for Fortran programs prior to F90.
  3. removing dead code: code that is never accessed.

refactorReengDIAG

Reengineering on the other hand could involve a port to a new platform, a translation to C, or a migration to Fortran 95.