MMAB C++  Class Library Descriptions
Robert W. Grumbine
Version 0.0
17 April 2000
MMAB Technical Note 186


Description of the Classes
Usage Notes
Definitions



Description
  • point
  • vector
  • Grid-Related classes
  • grid2_base
  • grid2
  • metric
  • cofs
  • chal
  • ncepgrids
  • grid3
  • grib
  • color
  • buoy

  • points

    points.h - classes point3, ijpt, fijpt

    Data members of point3<T>:
         T i, j, k;

    Operations in the class are:

    Declaration in your program:
    point3<type> x, x(5.0), x(1., 2.), x(1., 2., 3.);
    where 'type' is the type of variable you want x to contain.  May be any declared type, including
    int, float, double, unsigned char.


    vector
    vector.h:  classes vector, metricvector, time_series
    vector
    metricvector : public vector
    time_series   : public vector
     

    Each of these classes is templated.

    The vector class is a base class for vectors considered as mathematical, 1 dimensional, objects. This contrasts with the 'vector' which may be defined in a C++ installation, which is a somewhat different creature.

    The metricvector is a pair of vectors, one which carries values, and one which defines a coordinate associated with the values.  This might be, for example, a vector of the depths in a sounding and the associated temperature reading.

    The time_series class is experimental.  As the name suggests, it is directed towards time series operations.

    vector : Data members
        No public data members.
        You may find and change elements of the vector by using:
      value = x[int]; where x is your vector, and value takes the value of the vector at the [int] point.
      x[int] = value; which will put 'value' in to the vector.
     

    vector : Operations

    Declaration:
    vector<T> x, y(number of elements);

    metricvector : public vector -> this means that anything you can do to/with a vector, you can do to/with a metricvector
    Additional data:
      No public data.  The metric may be read or set by set_metric and get_metric.

    metricvector: Additional Operations:

    Declarations:
    metricvector<T> x, y(500);

    time_series : public vector  -> this means that anything you can do to/with a vector, you can do to/with a time_series

    time_series : Additional Operations:

    Declarations:
    time_series<T> x, y(500);
     


    grid-related classes:
    templated classes for working with 2d arrays.     Each class here inherits from the class above.  Anything a grid2_base can do, so can a grid2, a metricgrid, or any of the many ncepgrids.  Metricgrids add even more operations.  The ncepgrids, in general, don't add any new operations.  ncepgrids specialize the data segment of the class.  That is, the global_ice grid doesn't provide any new operations.  It is a latitude-longitude grid and can do whatever it is that such grids can do.  The new thing is the data -- the grid resolution, orientation, etc. -- are specified.

        Note here a side benefit.  I just said that the global_ice grid was a latitude-longitude grid.  Today, this is true.  Tomorrow, perhaps it won't be.  But, regardless, you won't have to rewrite your program at all (you would have to recompile, as things stand at the moment.  In the future even this may not be necessary.).  What you write is global_ice<float> x; to let x represent a global_ice grid filled with floating point values.  To read it in (for example), you write x.binin(input file).  You don't have to change this in your program, as your logic is unchanged.  What goes on behind the scenes to carry out your request, you don't care about as long as it works.  And the behind the scenes is the part that changes to the definition of global_ice affect.  If next week, the resolution changes, again, you don't change your program, just recompile.  (If you write your program making explicit use of the resolution, shame on you.  What you should use is x.dlat, x.dlon, the grid spacings of the grid.  Better yet, use the locate function since grid types other than latitude-longitude may not have these values.)


    grid2_base<T>:
    Data elements:
        No public data. Operations: Declaration:
    grid2_base<T> x, y(5), z(250, 728);
     


    grid2 : public grid2_base

    Note that below, even though we're now in the grid2 class, some of the arguments are given as being grid2_base.  This is because a grid2 _is_ a grid2_base.  (Think of grid2_base as being 'mammals' and grid2 as being 'carnivore'.  'Carnivore' can fill any spot that a 'mammal' can.)

    Data elements:
        No additional

    Operations:

    Declaration:
    grid2<T> x, x(5), x(70, 900);
     


    metricgrids

    The metricgrid class itself is a pure virtual class.  That is, there are certain operations which are common to all grids which have metrics, but they can't be written out because the operation depends on the actual metric.

    metricgrid : Data Elements: metricgrid : Operations:  llgrid: public metricgrid
    Data
        float dlat, dlon, firstlat, firstlon -- the grid spacing in latitude, longitude, and the location in latitude and longitude of the first grid point (0,0).  Southern latitudes are negative.  Longitude is measured positive to the east, i.e., 1 is 1 degree east.  Deltas may be negative (meaning that as the index increases,
    you move south, or west).
        Note: These parameters should, in the future, be made private so as to avoid accidental overwriting of their values.  (Though with accessors so that you can find out their values!)
     

    llgrid : Operations: -- required locate functions, and:

    Declaration:
    llgrid<T> x,  y(360, 180, 1.0, 1.0, -89.5, 0.5);
    (The second declares a 1x1 degree global grid centered on the half-integer points and starting from the south pole.  This happens to be the Levitus atlas grid.)
     
     

    psgrid : public metricgrid
    Data:
        No additional public data

    psgrid : Operations: -- required locate functions, and:

     Declaration:
    psgrid<T> x, y(385, 465, 190.0, 230.0,  60.0, -10.0, +1.0, 25.4e3, 25.4e3)
    The latter specifies the northern hemisphere sea ice analysis grid.  The grid is 385x465, with its pole at 190, 230.  The standard latitude of the projection is 60 N, and the standard longitude is 10 degrees east of 90 W (I agree this is awkward).  +1.0 denotes that this grid is in the northern hemisphere (-1.0 for the southern).  The grid resolution is 25.4e3 meters in x and y.  The standard latitude and longitude here are taken from the NCEP Handbook (standard longitude is to be 80 W).
     

    Experimental:

    mercator : public metricgrid
     

    rotllgrid : public llgrid
    Data:
        float rotation -- rotation angle in degrees.

    Operations: only required locates are present

    Declaration
     rotllgrid<T> x(30.0);
    x is rotated by 30 degrees.  (Currently assumes dimensions and values from Chalikov global ocean model.)
     


    ncepgrids:

    Grid NX NY d_lat d_lon first_lat first_lon Global?
    chalgrid 362 153 1.0 1.0 -80.5 -0.5 Yes
    chalreg 302 202 0.25 0.25 -.125 -100.125 No
    mrf1deg 360 181 -1.0 1.0 90.0 0.0 Yes
    global_otis 360 181 1.0 1.0 -90.0 0.0 Yes
    reg_otis 211 176 0.2 0.2 20.0 -82.0 No
    global_wave 144 59 -2.5 2.5 77.5 0.0 Yes
    NEW_global_wave 288 157 -1.0 1.25 78.0 0.0 Yes
    ECG_wave 133 121 0.25 0.25 15.0 -98.0 No
    ECG_hur 199 181 1/6 1/6 15.0 -98.0 No
    nh_ocean_weather 361 91 1.0 1.0 0.0 0.0 No
    nh_hazard 721 421 0.25 0.5 -20.0 -270.0 No
    global_ice 720 360 -0.5 0.5 89.75 0.25 Yes
    global_sst 360 180 -1.0 1.0 89.5 0.5 Yes
    levitus_atlas 360 180 1.0 1.0 -89.5 0.5 Yes
    global_ll 360 181 -1.0 1.0 90.0 0.0 Yes
    cfsreg 332 210 0.1 0.1 26.35 -83.05 No
    nopp 120 120 0.1 0.1 31.95 -81.05 No


    Cofs native grids:

    cfsgrid : public grid2
    -- specifies a 181 by 101 grid2.  No metric, but it is the right size for cofs native grids

    cfsnative : public metricgrid
    -- specifies the native cfs grid, with metric and correct size

    cfsnative: Data Elements

    cfsnative: Operations:


    Chalikov Native Grid

    chal_native : public metricgrid:

    chal_native : Data elements:

    chal_native : Operations



    grib:

    class grib_pds

    class grib_gds

    class grib_pds declares data and operations for the PDS portion of a grib message.  This is used by
    metricgrids for the purpose of writing out grib messages.

    grib_pts : Data
      pds[25] : array of the 25 elements which describe a PDS message

    grib_pds : Operations


    grib_gds : Data
        gdslen, gds[18]; // Length of the gds and the gds data array.
    All of these are declared by default, and must be overwritten in the construction of the data grids.  Experimental.  This is an undesirable  way to manage the type.
     


    grid3

    grid3 : public grid2

    grid3 is an impoverished class.  At the moment is it almost strictly a placeholder of sorts.  It's purpose is to declare a class suitable for working on 3 dimensional grids such as the global ocean temperature field.  Ultimate implementation should be quite different than the present as this version makes no real inheritance from the grid2 class.

    grid3 : Data elements


    grid3 : Operations


    buoy

    buoy is a class developed for working with buoy reports, particularly from the CPC buoy data files.  Note that as of 1 March 1997, the CPC coding changed, as did the permissable classes of reporting platform.  This date is a private part of the class, and functions like isbuoy will permit users to determine whether a report is a buoy report (as opposed to ship, e.g.) without remembering this date.
     

    class buoy_report

    buoy_report : Public Data

    buoy_report : Operations


    class avbuoy : public buoy_report

    This class makes it easier to average a number of buoy_reports.  As currently written, it only truly averages latitude/longitude information.

    avbuoy : Public data


    avbuoy : Operations




    color

    It is intended that this class be sufficient for handling color graphic processes.  The basic objects are RGB colors as ordered triples of point3<T> type.  T is unsigned char for 8 bit graphics.

    class palette

    palette : Public data


    palette : Operations


    Usage Notes

    The MMAB C++ library should be usable as any ordinary library, with only a couple of points of particular care needed.  First, and most significant, is that the MMAB library declared a vector class of its own.  It is necessary that it and the C++ standard library vector class not be used in the same program.  It is also necessary to be careful of defining "vector.h" rather than <vector.h>.

    Second, in order to use the cfsnative and chal_native classes, it will be necessary to get the ASCII data files for them and write them back out in your machine's native binary format.  More detail is given in the README file in interp.doc.tar.

    Third, it may be necessary to edit the files for platform dependances.  This is mostly a reflection of the fact that some of the omblib is in Fortran, and calling Fortran from C++ is platform dependant.  Defined platforms are SGI (Origin workstations tested), LINUX (Intel hardware and g++), and IBM (IBM SP).  If this is needed, the error message you'll get on attempts to link are things like arcdis_ not found (there is a Fortran function called arcdis, and it gets silently renamed by the compiler according to local conventions).

    Straightforward, but necessary, is that the sources in omblib.tar need to be compiled and placed in a library to which your programs can and will link.

    Includes
    mmabclass.tar

    Library
    mmabsource.tar
     
     


    Definitions

    bool, boolean:
        Standard type in C++ (not present on all systems, in particular our IBM SP).  Has values of true and false.

    Default values:
        Functions in C++ may be specified with default values.  Where they exist, the corresponding argument may be omitted.  The vector norm function has the default value of 2, so x.norm() and x.norm(2) are the same.  The defaults must be at the end of argument lists if there are non-default arguments present.

    Deprecated:
        Various operations in the classes are noted as being deprecated.  This is a reflection of the fact that the class library was developed over a signficant period of time, and some things that seemed like good ideas at the time, no longer do.  If you're writing new code, don't use these.  Alternatives are noted in the documentation.  If you have old code that uses these, on your next build you should replace these calls.  Deprecated features will be removed from the library in the future.  They are retained solely for backwards compatibility.

    Inheritance:
        The grid classes make extensive use of this feature of object oriented programming.  The basic idea is that an ncepgrid (for example) is some special type of grid -- it can do all the normal kinds of 'grid' things _as well as_ doing a few additional things which _only_ an ncepgrid can do.

    Overloaded Functions:
        We often encounter situations in which we want to do the same basic thing, but there may well be many different ways of doing it.  In Fortran, this sets up an undesirable situation.  Either we write a separate function -- with a different name! -- for each and every method, or we write a single mammoth routine which handles each and every case.  Neither is desirable.  The first requires the user to track down which of a dozen different routines he wants, and use a different name every time (the different names thereby obscuring what exactly is going on as it won't be so obvious from just the name).  The second requires the user, even in a case where he really does want the simplest version with the fewest options, to pass the full set of arguments (many of which may make no sense to someone who wants a default behavior).

        C++ manages this situation with overloaded functions.  The function name is left unchanged.  The compiler selects which version of the function you want by reading the argument list.  This permits the naming to remain clear, as in x.crossvary(y), which computes the cross-covariance between time_series x and y (default set up, no options), and x.crossvary(y, maskval, lag), which computes the cross-covariance between x and y again, but now skips masked points and works at a user-selected time lag.  The program invocation remains clear as to what is happening -- cross-covariances are being computed -- and the compiler, rather than the user, selects the right routine to carry out the desired action.

    Public:
        Through the documentation, I've noted that something is 'public'.  Part of the design in object orientation is to avoid some types of errors by making things 'private'.  The private things can be done inside the class, but cannot be done directly by the user.  This prevents the user from making certain kinds of mistakes.  For example, although users may read and modify the contents of arrays and can _find_ the dimensions of the array, they may not change on their own the dimensions of the array.  The information on array size is packaged with the array contents into the object.  The user may well want to operate on all the elements (or the first half in x and last half in y, or some other portion) of the array, but the operations themselves don't depend on being element 23, rather on being half way through the array.  nx needs to be knowable, but its value doesn't need to be changed by the user.
        If the array size itself were public, one has the problem (even if nobody else has made this mistake, I have) of passing the array size to a routine and accidentally changing it inside the routine (i.e, nx is the array size.  You mean to write limit = nx, but actually write nx = limit). The fact that nx is not public means that you couldn't make this mistake even if you tried.  If you want to change the size of the array, you _can_ do so, by asking the object to resize itself.
        Where classes are listed as being 'public someotherclass', this means that all the operations that you could do with an object of the 'someotherclass', you can do explicitly with the objects of the descendant class.  For example, grid2 inherits publicly from grid2_base, so all the grid2_base operations can be done by a user to a grid2 object.  All the classes currently in the MMAB class library use public inheritance.  There are reasons why one may want to use other types of inheritance, but they haven't yet occurred for classes we're working with.
     
     

    Pure virtual class:
    This is used to declare a class that you'll never actually have an object from, but which specifies things that all its descendants have to be able to do.  The metricgrid class is a pure virtual class.  All the descendants, latitude-longitude grids, polar stereographic grids, the ROFS native grid, etc., have to write their own functions to provide mapping between grid coordinates and latitude-longitude coordinates.  We simply don't know ahead of time how this will be done for new grid classes, but any metric grid _must_ do this.  The benefit is that _if_ there is such a mapping, we can do other things and write (and debug!) the algorithm for it in the metricgrid class.  One example is grid to grid interpolation.  The _logic_ is the same, regardless of the two grids involved.  That is, do a bilinear interpolation from the data source grid onto the destination grid given the location of the destination point in the i,j space of the data grid.  Bilinear interpolation is a known and solved problem, so we write the routine once, debug it.   Then regardless of what new grid we try to work on, once we write the functions for mapping between physical (lat-long) and grid (i,j) space, we can interpolate from grid to grid.
     

    Template:
    Abstraction is a major reason for working in C++.  When you have a function which performs the same logical operations regardless of the data types it is working on, you want to write (and debug!) only one such function.  In Fortran, for example, if you want to compute the average value of an array (say) you need different routines for an array of integers, an array of floating point numbers, and an array of complex numbers.  Logically, this makes no sense, as averaging is defined without regard to data type.  In C++ you would use a template function:
    template <class T>
    T average(grid<T> x)
    and inside the function, simply write the logic for how to average a grid x which has values of type
    T.  And you don't, in writing the function, need to consider what T is.  This example is taken from the grid_math class.