Modeler's Introduction to NetCDF

The Goal

So you have a model and want it to write NetCDF? Here's the page I wish I had when I started out.

Stuff you absolutely need to know if you're new to NetCDF

Getting familiar with NetCDF

Writing CDF the (sort of) easy way

Part I: Know what CDL is

Part II: Understanding ncgen and ncdump

Part III: generating header files

Part IV: Understand how to write a record

This is the easy way to write a record of floats:

When you automatically generated your code, it essentially produced a netCDF handle (here, ncid):

   int stat = nc_create("c5.nc", NC_CLOBBER, &ncid);
and a NetCDF tag for each variable (here, drom_id):
  drom_dims[0] = x_dim;
  drom_dims[1] = y_dim;
  stat = nc_def_var(ncid, "drom", NC_FLOAT, RANK_drom, drom_dims, &drom_id);
  check_err(stat,__LINE__,__FILE__);
With these two things defined, you can now write your array:
   nc_put_var_float(ncid,drom_id,&(drom[0][0]));
   check_err(stat,__LINE__,__FILE__);
The above writes the float array where it goes. When you have written all your fields as above you This is the only code you have to write by hand!

Note, There's comparable routines for nc_put_var_type where "type" is one of byte short int float double . If you prefer you can use real as a synonym for float.

Note, in case you didn't know (I didn't) __LINE__ and __FILE__ are system constants provided by the preprocessor. This facilitates reporting of runtime errors.

Part V: OK, now you're ready

Here are the four quasi-simple steps you need to take to splice NetCDF writes into your model the easy way: See the example below and follow along, keeping the four steps in mind.

Is it worth it?

If you want to use the tools that analyze and display NetCDF, yes. If your output is real science that you intend to keep around and publish, yes. If you're just messing around, probably not.

A Simple Example (which is something that, as far as I can tell you can't find anywhere else).

What Gives

I'm a fan of code generation in general. I don't think humans should write tedious code at all. So I'm using the ncgen tool here.

However, let me emphasize that the case below is simple enough that you can follow it as a prototype. You don't need to use ncgen if you prefer writing C directly. That said, I can't resist asserting that this is a perfect example of a case where using a code generator is a big win.

Step 0: Pre-existing model or analysis code.

Here I build a couple of arrays. (Ignore the variable names, they are for my own peculiar amusement.)

float drom[4][4];
float bact[4][4];

main()
{
int i,j;

printf("hello\n");

for (i=0;i<4;++i)
        for (j=0;j<4;++j)
        {
                drom[i][j] = 10. * i + j;
                bact[i][j] = 10. * j + i;
        }
}

Step 1: I build this CDL file to match

I call it c5.cdl

netcdf camelsrc{
dimensions:
        x = 4,
        y = 4;
variables:
        real drom(x,y);
        real bact(x,y);
}

Step 2: Generate code

ncgen -c c5.cdl > c5src.c 

This gives me the automatically generated c code:

/* Include standard and NetCDF libraries */

#include <stdio.h>
#include <stdlib.h>
#include <netcdf.h>
/* Error handling routine */
void
check_err(const int stat, const int line, const char *file) {
    if (stat != NC_NOERR) {
	   (void) fprintf(stderr, "line %d of %s: %s\n", line, file, nc_strerror(stat));
        exit(1);
    }
}

int
main() {			/* create camelsrc.nc */
/* Set up all sorts of tags and handles. Notice there is no actual data in this program! This just creates the CDL header. */
   int  ncid;			/* netCDF id */

   /* dimension ids */
   int x_dim;
   int y_dim;

   /* dimension lengths */
   size_t x_len = 4;
   size_t y_len = 4;

   /* variable ids */
   int drom_id;
   int bact_id;

   /* rank (number of dimensions) for each variable */
#  define RANK_drom 2
#  define RANK_bact 2

   /* variable shapes */
   int drom_dims[RANK_drom];
   int bact_dims[RANK_bact];

/* create the NetCDF handle, akin to opening the file */
   
   /* enter define mode */
   
   int stat = nc_create("camelsrc.nc", NC_CLOBBER, &ncid);
   check_err(stat,__LINE__,__FILE__);
/* declare the variables */
   
   /* define dimensions */

   stat = nc_def_dim(ncid, "x", x_len, &x_dim);
   check_err(stat,__LINE__,__FILE__);
   stat = nc_def_dim(ncid, "y", y_len, &y_dim);
   check_err(stat,__LINE__,__FILE__);

   /* define variables */

   drom_dims[0] = x_dim;
   drom_dims[1] = y_dim;
   stat = nc_def_var(ncid, "drom", NC_FLOAT, RANK_drom, drom_dims, &drom_id);
   check_err(stat,__LINE__,__FILE__);

   bact_dims[0] = x_dim;
   bact_dims[1] = y_dim;
   stat = nc_def_var(ncid, "bact", NC_FLOAT, RANK_bact, bact_dims, &bact_id);
   check_err(stat,__LINE__,__FILE__);

   /* leave define mode */

   stat = nc_enddef (ncid);
   check_err(stat,__LINE__,__FILE__);
/* now close the file and exit */
   stat = nc_close(ncid);
   check_err(stat,__LINE__,__FILE__);
   return 0;
}

Step 3: Write the code to write the fields

I'm writing two fields:
   nc_put_var_float(ncid,drom_id,&(drom[0][0]));
   check_err(stat,__LINE__,__FILE__);
   
   nc_put_var_float(ncid,bact_id,&(bact[0][0]));
   check_err(stat,__LINE__,__FILE__);

The above is the only new code I wrote by hand (not counting the original model)!

Step 4: Now splice it all together.


KEY:



#include <stdio.h>
#include <stdlib.h>
#include <netcdf.h>

float drom[4][4];
float bact[4][4];

void
check_err(const int stat, const int line, const char *file) {
    if (stat != NC_NOERR) {
	   (void) fprintf(stderr, "line %d of %s: %s\n", line, file, nc_strerror(stat));
        exit(1);
    }
}


main()
{
int i,j;

for (i=0;i<4;++i)
	for (j=0;j<4;++j)
	{
		drom[i][j] = 10.*i + j;
		bact[i][j] = 10.*j + i;
	}

   int  ncid;			/* netCDF id */

   /* dimension ids */
   int x_dim;
   int y_dim;

   /* dimension lengths */
   size_t x_len = 4;
   size_t y_len = 4;

   /* variable ids */
   int drom_id;
   int bact_id;

   /* rank (number of dimensions) for each variable */
#  define RANK_drom 2
#  define RANK_bact 2

   /* variable shapes */
   int drom_dims[RANK_drom];
   int bact_dims[RANK_bact];

   /* enter define mode */
   int stat = nc_create("camelsrc.nc", NC_CLOBBER, &ncid);
   check_err(stat,__LINE__,__FILE__);

   /* define dimensions */
   stat = nc_def_dim(ncid, "x", x_len, &x_dim);
   check_err(stat,__LINE__,__FILE__);
   stat = nc_def_dim(ncid, "y", y_len, &y_dim);
   check_err(stat,__LINE__,__FILE__);

   /* define variables */

   drom_dims[0] = x_dim;
   drom_dims[1] = y_dim;
   stat = nc_def_var(ncid, "drom", NC_FLOAT, RANK_drom, drom_dims, &drom_id);
   check_err(stat,__LINE__,__FILE__);

   bact_dims[0] = x_dim;
   bact_dims[1] = y_dim;
   stat = nc_def_var(ncid, "bact", NC_FLOAT, RANK_bact, bact_dims, &bact_id);
   check_err(stat,__LINE__,__FILE__);

   /* leave define mode */
   stat = nc_enddef(ncid);
   check_err(stat,__LINE__,__FILE__);



   /* now write them */
   
   nc_put_var_float(ncid,drom_id,&(drom[0][0]));
   check_err(stat,__LINE__,__FILE__);
   
   nc_put_var_float(ncid,bact_id,&(bact[0][0]));
   check_err(stat,__LINE__,__FILE__);



   /* close the file */
      
   stat = nc_close(ncid);
   check_err(stat,__LINE__,__FILE__);

}

Here's a tarball of the whole kaboodle

tarball



More Things About NetCDF that Might Prove Useful


Comments

Dessert

I needed a code-to-html filter to keep these notes going. Here it is.
My home page such as it is.