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:
PROGRAM rbc USE, INTRINSIC :: iso_Fortran_env, ONLY: wp => real64 IMPLICIT NONE 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 CLOSE(iunit)
The above will save the array to disk, in full
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 CLOSE(iunit)
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 ... CLOSE(iunit)
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;
v0? We know its dimensions, since we just read the parameters, including
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); fclose(fileid); out = reshape(out, sz); end
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
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
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