A Not-too-horrifying Fortran+MATLAB Workflow
by Gabriel Mihalache

This is a quick note on a mostly pain-free way of saving and loading data for your Fortran code, with the best possible precision, while also having the option to load the data in MATLAB (or similarly in any other "high level" language) with minimal hassle. As usual, your milleage may vary, caveat emptor and so on.

Say you have some fairly standard Fortran code, like this:

  USE, INTRINSIC :: iso_Fortran_env, ONLY: wp => real64
  INTEGER, PARAMETER :: zSz = 25, kSz = 101
  CHARACTER (LEN=*), PARAMETER :: outDir = "./results/"
  REAL(wp), PARAMETER :: crra = 2.0_wp
  INTEGER :: iunit
  REAL(wp), DIMENSION(zSz, kSz) :: v0, v1

Eventually you will want to save your results, here the v0 array, for example:

OPEN(NEWUNIT=iunit, FILE=outDir // "v.bin", FORM="unformatted", ACCESS="stream", STATUS="unknown")
WRITE (iunit) v0

The above will save the array to disk, in full real64 (a.k.a. wp) precision, binary, without the need for specifying a format. Later, if you want to read back in this data, say for an initial guess, you can do so using something like:

OPEN(NEWUNIT=iunit, FILE=outdir // "v.bin", FORM="unformatted", ACCESS="stream", STATUS="old")
READ (iunit) v0

That's pretty comfy. Now what about bringing those results into MATLAB for visualization, plotting, whatever? We should bring over all relevant parameters, array sizes, and the arrays themselves. First, let's write to a plain text file the parameters and dimensions of the problem, from Fortran:

OPEN(NEWUNIT=iunit, FILE=outDir // "parameters.txt")
WRITE (iunit, "(I20)") zSz
WRITE (iunit, "(I20)") kSz
WRITE (iunit, "(E20.10)") crra

Here we do need to specify formats, because of the plain text format, but since we're only saving a handful of numbers we can live with it. I20 should be enough for any integer (up to 20 characters in length) and E20.10 will use scientific notation for our real64-s. Now we're ready to read it in, in MATLAB:

params = dlmread('parameters.txt');
ix = 1;
zSz = params(ix); ix = ix + 1;
kSz = params(ix); ix = ix + 1;
crra = params(ix); ix = ix + 1;
clear params ix;

What about v0? We know its dimensions, since we just read the parameters, including zSz and kSz, so we should use that programatically, in case we want to change the dimensions of the problem in future runs:

v = loadBinary('v.bin', 'float64', [zSz, kSz]);

Note that throughout I assumed that the MATLAB scripts are in the same directory/folder as the output of the Fortran code. Otherwise, adjust your paths accordingly. What about this loadBinary function and 'float64', what's that about? The function we need to write outselves, just once, for our convenience:

function [ out ] = loadBinary(fname, type, sz)
    fileid = fopen(fname, 'r');
    out = fread(fileid, [prod(sz), 1], type);
    out = reshape(out, sz);

The above function will load binary data from disk, based on the size and shape provided, then reshape it and return it to you neatly. We're not doing any error correction or attempt to gracefully recover from dimension/shape errors because if the sizes don't match, execution should stop with an error.

Concerning data types and their size in bytes... Fortran's real64 (also known as DOUBLE REAL to some) matches MATLAB's float64. Fortran's INTEGER goes with int32 in MATLAB, and so on. The MATLAB documentation of fread lists the data types. If you're trying to use the wrong data type, the size of the file will not match the fread call and you'll get an error from MATLAB. You can check out this handy page on the Fortran Wiki about what predetermined data types you can find in iso_fortran_env, my recommendation is to stick with those, for both REALs and INTEGERs. I am also enthusiastic about the use of LOGICAL variables to encode settings/flags.

A final note... if your code uses the GPU, often GPUs have far better performance in "single precision" mode, so you might want to use that instead:

USE, INTRINSIC :: iso_Fortran_env, ONLY: wp => real32

Back to my website