Interfaces

CHLone is a library, there is no tool, no application. Three levels of interfaces are available in CHLone, C1 is the higher level, it is the CGNS MLL Clone same API as CGNS/MLL with a different implementation F2 the Fortran Facilities, an interface designed for Fortran users, L3 the Low Level Library, manages HDF5 nodes.

CHLone also includes its own Python interface, it is not an API but rather an translator from/to CGNS/Python. Two functions, load and save, provide the user with a simple but versatile entry door for CGNS data manipulation in Python. See python CHLone module.

_images/libstack.png

The schema above shows the relationships between the three CHLone interfaces and the existing application . The L3 is the node management, all other interfaces are built on top of it. The SIDS-to-Python interface offers a Python module so-called CHLone, which can load/save CGNS/Python complete trees as lists of lists, containing plain strings or numpy arrays. The F2 is for Fortran users but actually has a C interface as well, nodes are selected and manipulated by means of their paths. The C1 is built to provide the user with exactely the same interface as CGNS/MLL, thus allowing the user to change its CGNS implementation with a simple relink of his programs.

L3 interface

The L3 interface is the Low Level Library. It is not intend to be used by developpers, the target CHLone interfaces are F2 and SIDS-to-Python, the C1 interface is of lower priority.

The CGNS data structure is based on a tree. Each node can contain a value and may have a set of children. The basic data structure of L3 is this node, in the L3 interface you can find function to handle the data of this node and to parse its children.

Each L3 function handles only one L3 node. The access to the target file is performed using a context, it is a required argument to all L3 functions. The L3 node itself is a C structure used to gather all the required arguments for an L3 function call. Each call actually read/writes data in/from HDF5, there is no data buffering. The actual data pointer, for example for a coordinates array can be a new pointer coming from the L3 layer or an existing pointer passed as an argument to the L3 layer. This actual pointer is the one used by the HDF5 calls, then there is no memory copy or buffering, which is helpfull when you are dealing with very large amount of data.

Open and close a context

The L3_openFile function is the first you call. It returns a context you should give as argument to the forthcoming L3 calls. You close or free a context using L3_close function.

L3_Cursor_t *cgnsfile;

cgnsfile=L3_openFile("M6wing.cgns",L3E_OPEN_NEW,L3F_OPEN_DEFAULT);
L3_close(cgnsfile);

The second argument indicates if the open has to create a new file or has to open an previously created file (or a so-called ‘old’ file). You can open an existing file using the L3E_OPEN_OLD flag instead of L3E_OPEN_NEW . If you try to access an ‘old’ file using the new flag, or to create a ‘new’ file without the new flag, an error occurs. The open function returns a NULL context if an error occurs. Please, refer to section about error management to know how to detect and get more about errors. All subsequent L3 calls using a NULL context with return without complaining, only the first error found is kept.

A simple way to test if an error occurs is to use the L3M_ECHECK macro, which returns 1 if there is NO error. You can also print it, if you use to the standard output, with the printError function:

L3_printError(cgnsfile);

The context can be created using L3_openHID. The function uses an existing hid_t as root id or the CGNS tree, then you don’t need to provide a filename to read or write a CGNS/HDF5 tree, you just have to find an HDF5 object acting as a root of your tree. The user has to check if the hid_t can be used for such a purpose. For example, you can open an in-memory HDF5 or even have your own HDF5 driver:

L3_Context_t *cgnstree;
cgnstree=L3_openHID(rootid);

The L3_close has no action on such a rootid. All the L3 functions will accept the context without restriction on the way you get the actual root id.

Create and delete a node

Once you have a context, you can manage the nodes. We mean the L3 node, not the actual HDF5 node. In practice, these nodes very close but we need an intermediate structure in L3 to hide some implemention details and to make the user believe its node is a CGNS low level node.

An actual node is identified with its ID, of type hid_t. This ID is the HDF5 ID, you can also use it for any HDF5 API call. The root id of the current context you have is stored in the root_id member of your context. The way to create, read or update a node is to use a L3_Node_t descriptor. The contents of a node is always the same, whichever type of CGNS SIDS node it is. The table below shows this contents.

Attribute Type Remark
name string (1)
label string (2)
dimensions array of integers  
datatype string (3)
data depends on datatype (4)
flags integer (5)

Remarks:

1 Name of the node The name can have any ASCII char except the [/] <slash> and the [>] <greater than>. The first is used to separate node names in a path, the second is internally used by CGNS/HDF5 for the symbolic links. The [ ] <space> and [.] <dot> are allowed except as the first character of the name.

2 CGNS/SIDS type of the node Usually it is a capitalized string with the [_t] postfix.

3 CGNS/SIDS type of the data in the node The CGNS/SIDS datatypes are:

  • C1 a string (that is an array of characters, the array should not contain a trailing [\0]).
  • I4, I8 integer and long integer
  • R4, R8 float and double float.

4 Actual data A contiguous array of data using the so-called Fortran indexing

5 A bitfields as an integer The value 0x0001 indicates that the FORTRAN flag is set. In that case, the node has been written using the MLL library. See section The L3_Node_t structure has other attributes, see section implementation remarks about these.

All the CGNS/SIDS types and some names of its annex A have constants. The names are postfixed with [_n] and the types with [_ts]. The next code snippet shows a call to L3_nodeSet. It returns a new pointer to a L3_Node_t structure with the arguments:

L3_Context_t cgnsfile;
L3_Node_t node;
int dims[2]={1,-1};

node=L3_nodeSet(cgnsfile,NULL,
                CGNSLibraryVersion_n,CGNSLibraryVersion_ts,dims,L3E_R4,data)

The first argument is the context, the second is the L3_Node_t where to set the values. In that case, we pass NULL, this means we have no L3_Node_t to set, we want a new one. In all cases, the returned pointer is the L3_Node_t that has been set. The arguments 3 and 4 are the name and the label of the node, here we set the CGNSLibraryVersion node. Then comes the dimensions of the data and its type, here we have a single one float. The data itself, the last argument, is the float. The dimensions are stored into an integer array, it is read until it reaches a -1 value, indicating that the rest of the data is not relevant. Now we can create an actual CGNS/HDF5 node, we use the L3_nodeCreate fonction:

hid_t cgnslibraryversion;

cgnslibraryversion=L3_nodeCreate(cgnsfile,cgnsfile->root_id,node);

The first argument is the context, the second one is the parent of the node to create, in that case we use the context to get the root ID of the current file. The returned value is the new CGNS/HDF5 node ID. This two steps creation is useful when you have a lot of similar nodes to create, or when you want to update. The example hereafter shows an incomplete example for the creation of some CGNS zones with the same dimensions. The NULL arguments passed to L3_nodeSet let the previous value of the node unchanged:

node=L3_nodeSet(cgnsfile,NULL,NULL, Zone_ts,dims,L3E_I4,data);
L3_nodeCreate(cgnsfile,base,L3_nodeSet(db,node,"Zone-001",NULL,NULL,NULL,NULL));
L3_nodeCreate(cgnsfile,base,L3_nodeSet(db,node,"Zone-002",NULL,NULL,NULL,NULL));
L3_nodeCreate(cgnsfile,base,L3_nodeSet(db,node,"Zone-003",NULL,NULL,NULL,NULL));

The deletion of a node requires its parent ID and its name, no L3_Node_t:

L3_nodeDelete(cgnsfile,base,"Zone-003");

The deletion is performed by the HDF5 library, all children nodes are recursively deleted.

Find and modify a node

The L3_Node_t has been filled with data to create a new node. You can also use this structure to update an existing node. First, you have to retrieve the information of an HD5 object and fill the L3_Node_t node with it:

L3_Context_t *cgnsfile;
L3_Node_t    *node;
hid_t         id;

node=L3_nodeRetrieve(cgnsfile,id);

The L3_Node_t node found contains its name, label, dimensions and data, these are all the data used for creation. You can display a node content on the standard output using the L3_nodePrint. The output is limited to the name, the label and the data type:

L3_nodePrint(node);

The node contains more thant these data. For example, the list of children Ids are also stored into the L3_Node_t. You can loop on this list, you have to use the children attribute of the returned L3_Node_t, it is an array of hid_t Ids, one per actual child. The list ends with a -1 value instead of a valid hid_t:

L3_Node_t    *childnode;

node=L3_nodeRetrieve(cgnsfile,id);
while ((node->children != NULL) && (node->children[n] != -1))
{
  childnode=L3_nodeRetrieve(cgnsfile,rode->children[n]));
  n++;
}

The L3_Node_t you get using the L3_nodeRetrieve also has the ID you used to retrieve it. You have not this information in a L3_Node_t you created with L3_nodeSet. You can change the values in this L3_Node_t in order to update the HDF5 object:

L3_nodeSet(cgnsfile,node,"New Name",NULL,NULL,NULL,NULL);
L3_nodeUpdate(cgnsfile,node);

The ID is not an argument of the function, it is in the L3_Node_t structure. The example above acts like a rename of the HDF5 object. A simple rename, not a move, in other words the parent HDF5 object is unchanged. If you intend to move your node, that is to change its father, you have to use the L3_nodeMove function:

L3_nodeMove(cgnsfile,parentid,newparentid,"name","newname");

The first argument is the context, the second is the current parent of the node to move. The third is the new parent of the node. The two last strings are the current name and the new name. You can also update a C variable with the contents of an existing L3_Node_t. The L3_nodeGet function takes C variables as arguments, these are updated with the values found in the L3_Node_t structure:

int dims[L3_MAX_DIMS];

node=L3_nodeRetrieve(cgnsfile,id);
L3_nodeGet(cgnsfile,node,NULL,NULL,dims,NULL,NULL);

The L3_nodeFind function looks for a child node starting from a given HDF5 object. The returned value is a hid_t not a L3_Node_t. The third argument of the find is a path defined as the classical string. The example hereafter shows the composition of L3 function calls, used to get an hid_t and then to fill the L3_Node_t:

L3_nodeRetrieve(cgnsfile,L3_nodeFind(cgnsfile,id,"./Zone-001/ZoneBC"))

Other functions

You can get the HDF5 id of a object using its path and retrieve the path of an object giving its HDF5 id. The two functions are L3_path2Node and L3_node2Path. The L3_isSameNode unction checks wether to hid_t are refering to the same HDF5 actual object. The two functions L3_typeAsStr and L3_typeAsEnum are used to translate the HDF5 data type into the L3 string or the L3 enumerate. Some convenience functions are used to initialize or set dimensions and data:

L3_initDims(dims,3,3,-1);
data=L3_initData(dims,data,L3E_I4,9,3,5,8,2,4,0,0,0);
data=L3_fillData(dims,data,L3E_I4,0);

About flags

The flags are an important topic of the L3 interface. The behavior of the interface can be changed just by setting or unsetting one flag. The flags are associated with the context, each L3 function looks at the context flags, so that you can change flags between two function calls. You can change the flags using the L3_setFlags and retrieve them with L3_getFlags. A set of macros are useful, the L3M_HASFLAG, L3M_SETFLAG and L3M_UNSETFLAG.

Flag Usage  
L3F_NONE All flags set to False x
L3F_NULL Unsignificant flags value x
L3F_WITHDATA Load/Save DataArray_t 1
L3F_WITHCHILDREN Add children in the process, not recursive 1
L3F_FAILSONLINK Raises an error if the link is not reachable 1
L3F_FOLLOWLINKS Traverse the link and continue the process 1
L3F_MERGELINKS Reserved 0
L3F_COMPRESS Compress DataArray_t 0
L3F_SKIPONERROR First error forces skip of following L3 calls 1
L3F_OWNDATA Reserved 0
L3F_NOCHILDMERGE Reserved 0
L3F_WITHLINKINFO Reserved 0
L3F_WITHMUTEX Use the mutual exclusion locks 1
L3F_FILLNANDATA Fill created DataArray_t with NaN 1
L3F_BULLETPROOF Force checks on args and functions returns 0
L3F_HASEXTFLAGS Extension flags are present 0
L3F_DEBUG Verbose trace with internal data debug 0
L3F_TRACE End-user trace 0

A flag is an integer. The default flags are set using the L3F_DEFAULT value, which sets:

L3F_FOLLOWLINKS|L3F_WITHDATA|L3F_WITHMUTEX|L3F_WITHCHILDREN|L3F_SKIPONERROR

Error management

CHLone defines its own set of error codes. The L3 interface, which is the closest to HDF5 calls, traps the HDF5 errors and translates them in CHLone codes. You can ask to CHLone to let HDF5 error stack appear if you set the TRACE flag. The error functions of the CHLone interface are:

void   CHL_printError (L3_Cursor_t *ctxt);
void   CHL_setError   (L3_Cursor_t *ctxt, int err, ...);
int    CHL_getError   (L3_Cursor_t *ctxt);
int    CHL_setMessage (L3_Cursor_t* ctxt,char *msg);
char  *CHL_getMessage (L3_Cursor_t* ctxt);

If you want a more detailled explanation of what an error code means, you can refer to the table in annex.

Interface summary

The interface contents with all enumarates, macros, functions, error codes.

F2 interface

The F2 interface is a node-based interface with fortran oriented functions. The user application parses a CGNS/HDF5 tree by means of paths, it stores selected paths in stacks and access to nodes using both stack or paths. The node is the actual data the application wants, a node may contain an array and F2 provides node array manipulation too.

The parsing of a tree is an extra processing added to usual applications. The application can performs F2 node selections using path-based functions. A set of paths can be extracted from a complete tree with respect to a pattern including specific tokens such as plain node name, plain node type or even regular expressions. See the section about path querying hereafter.

Entity Purpose
ID A CGNS/HDF5 node as a unique identifier (the HDF5 hid_t). This ID is a key to a node as well as the Path.
Path A string that identify a node in a CGNS/HDF5 tree. The Path is a suite of token`s separated by the `/ character.
Token A node name, this string may not be unique in all the CGNS/HDF5 tree.
Stack An ordered list of `Path`s, used for application purpose. For example you store the X,Y and Z coordinate nodes’ paths in a stack and you manipulate the stack as a whole instead of each node.

The interface use is then to define or to find out paths, to get corresponding IDs and to call functions with these IDs as arguments. For example, you want to get dimensions for all structured zones in a base.

F=1
S=1
path="/CGNSTree_t/Base#01/Zone_t/ZoneType_t"
nzones=f2_stackFetchByString(F,S,path,"Structured")
do n=1,nzones
  f2_nodeDimensions(F,f2_stackParentId(F,S,n))
enddo

S=2
path="/CGNSTree_t/Base#01/Zone_t/GridCoordinates/^Coordinates[XYZ]$"
ncoords=f2_stackFetch(F,S,path)
f2_arrayAsContiguous(F,path,T)

Context

A Context is more or less a CGNS/HDF5 file. It could be more in the case of links, but we approximate here as one context is one file. Most F2 functions do require a context as first argument, then the first function you have to call is the F2_open_f which opens a context related to the given filename. The returned value C is the index of the context in the F2 internal tables.

integer C

call F2_open_f("/tmp/file.cgns",1,-1,C)
call F2_close_f(C)

The remaining arguments are unused here, the first 1 is the flags and the -1 is the root id. You can have more than one context, and opening, using and closing a context is thread-safe.

Paths and Stacks

A path is a string used to identify a node in a CGNS tree. The path syntax is the same as the Unix file system syntax, and obviously the same as the HDF5 syntax.

Note

The path we use in CHLone is different to the HDF5 node path as we have the root node, CGNSTree of type CGNSTree_t. If you remove this first token of the path, you actually have the HDF5 path.

A path is composed of tokens separated by the / char. A token can be a plain name token, a plain type token or a regular expression token.

A plain name token is a token used to select a node name matches exactly the characters.

A plain type token is a token used to select a node SIDS type instead of its name. For example GridCoordinates_t can be used to match all nodes having this string as SIDS type. The token should have _t as the two last characters to make CHLone detect it is a SIDS type token.

A regular expression token is a used to select node names given a regular expression. The first character should be a ^ and the last should be $, this is the way CHLone detects a regular expression token to the other token types.

The examples hereafter show usual patterns used for a CGNS tree parsing. Some paths may represent a set of paths instead of a single one, we use the term matches to say a node actually belong to the set defined by the path.

A plain path, all tokens are plain tokens. Then it should lead to a single node, here it matchesThe X coordinates of the zone D.001 in the base Flap.

/CGNSTree/Flap/D.001/GridCoordinates/CoordinatesX

A path with both a type selection and a regular expression. The third token Zone_t defines the type of the node rather than its name. Then all nodes with the SIDS type Zone_t would match. The last token is a regular expression, enclosed by ^ and $, that defines a plain string Coordinates and a set of possible characters X,``Y`` or Z. All X,Y and Z coordinates would match this token. The result of the path search in a CGNS tree is all the coordinates of all the zones of the base Flap.

/CGNSTree/Flap/Zone_t/GridCoordinates/^Coordinates[XYZ]$

When the result contains more then one path it needs to be stored in a list. The F2 interface defines stacks for that purpose. All the returning path, even if there is only one, is stored in the stack. You can use the stack for operations on a set of nodes instead of a single one.

A stack is identified by an integer index. You can have many stacks, you have to select the stack you operate on by specifying its index. In the exemple hereafter, C is the context and S is the stack.

integer C,S

S=1
path="/CGNSTree/Flap/Zone_t/GridCoordinates/^Coordinates[XYZ]$"

call F2_open_f("/tmp/file.cgns",1,-1,C)
call F2_stackClear(C,S)
call F2_stackFetch(C,S,path)
call F2_close_f(C)

You have to retrieve the actual node from its path if you want to operate on its data.

Errors

Interface summary

The interface contents with all enumarates, macros, functions, error codes.

C1 interface

The C1 interface is the exact copy (or clone) of the CGNS/MLL interface. The file cgnslib.h provided by CHLone is a copy of the original file with an add include to the CHLone.h header. The CHLone interface defines the CHLONE_IMPLEMENTATION, so that you can detect at preprocessing time wether you are including the original or the clone.

CHLone vs CGNS/MLL

The CGNS/MLL stores the actual node ids and returns its own indexes. CHLone returns the actual node id. The CGNS/MLL buffers the data, it creates its own data structure as a copy of the CGNS tree you have on disk. Check operations are performed on the buffered data. CHLone calls actually read/write the data on/to disk.

Warning

The C1 interface is not ready to use today.

Compatibility

The interface is 100% compatible. This means you can recompile your CGNS/MLL application without modification and obtain the same results. The CHLone lib has, however, a different implementation. If your application uses some CGNS/MLL tricks based on the legacy implementation, the change to CHLone may result in changes in your results. In particular if you bet on indexes returnd by CGNS/MLL.

Interface summary

The C1 interface does not cover 100% of the CGNS/MLL interface. The reason it is so is that the CGNS/Python interface is far more used by CHLone users. Then the effort is, today, focused on the CHLone python module.

The implemented functions, as of v0.47 of CHLone, are:

  • cg_open
  • cg_close
  • cg_nbases
  • cg_base_read
  • cg_base_write
  • cg_zone_write
  • cg_zone_type
  • cg_zone_read
  • cg_nzones
  • cg_ncoords
  • cg_coord_info
  • cg_coord_read
  • cg_coord_write