.. -------------------------------------------------------------------------
.. pyCGNS - CFD General Notation System - 
.. See license.txt file in the root directory of this Python module source  
.. -------------------------------------------------------------------------

.. _mapexamples:

MAP Examples
++++++++++++

The *CGNS.MAP* provides the user with the two functions ``load`` and ``save``,
these are direct mapping of 
the `CHLone <http://chlone.sourceforge.net/pythonCHLone.html#pythonchlone>`_ 
functions.
The actual actions these functions are performing heavily depends on the
arguments you pass to these functions, the ``load`` takes a *CGNS/HDF5* tree
and returns at least a *CGNS/Python* tree, while the ``save`` takes a
*CGNS/Python* tree and writes (modifies) a *CGNS/HDF5* tree.
Rather than listing all options, we suggest your try to figure out how to
use these functions reading the examples.

All the examples are assuming the following imports::

  import CGNS.MAP              
  import CGNS.PAT.cgnsutils    as CGU
  import CGNS.PAT.cgnskeywords as CGK
  import CGNS.PAT.cgnslib      as CGL

  import numpy as NPY

Complete load of a *CGNS/HDF5* file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The translation of a *CGNS/HDF5* file into a *CGNS/Python* tree is
performed with the simple line::

  (tree,links,paths)=CGNS.MAP.load("mesh.cgns")

If the ``testfile.cgns`` has no link the ``links`` and ``paths`` are
empty lists and ``tree`` contains the *CGNS/Python* tree.
The file name extension is not required, if the file doesn't exist
or if it is not a *CGNS/HDF5* file exceptions are raised::

  >>> T=CGNS.MAP.load('a.hdf')
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "pyCHLone.pyx", line 137, in CHLone.load (pyCHLone.c:2130)
  CHLone.CHLoneException: (900, 'No such file [a.hdf]')

  >>> T=CGNS.MAP.load('cgnscheck.pdf')
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "pyCHLone.pyx", line 151, in CHLone.load (pyCHLone.c:2398)
  CHLone.CHLoneException: (101, 'Target is not an HDF5 file [cgnscheck.pdf]')

You can see here the exception are coming from ``CHLone`` interface,
but as *CGNS.MAP* wraps *CHLone* you can also catch them as a *CGNS.MAP*
error::

  >>> try:
  ...   T=CGNS.MAP.load('cgnscheck.pdf')
  ... except CGNS.MAP.error, e:
  ...   print e
  ... 
  (101, 'Target is not an HDF5 file [cgnscheck.pdf]')

And if you write your own function, this leads to::

  def myload(filename):
    try:
      T=CGNS.MAP.load('cgnscheck.pdf')[0]
    except CGNS.MAP.error, e:
      T=None
    return T

In this latter example, we are forgetting ``links`` and ``paths``.

Complete save of a *CGNS/Python* tree
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now you have a *CGNS/Python* tree of your own, use the ``save`` 
to write the contents in a *CGNS/HDF5* file::

 CGNS.MAP.save("solution.hdf", tree)

Again, the file extension is not significant. You can save your file with 
the name you want as far as the file system is happy with it::

  CGNS.MAP.save('../solution.cgns',T)
  CGNS.MAP.save('RESULT',T)
  CGNS.MAP.save('solution.doc',T)

Of course, all these files are *HDF5* files, and you may have problem
with tools is you play with file names. Then we strongly suggest you
stay with usual file extensions such as ``cgns`` or ``hdf``.

If the file exist or if your file system doesn't want you to write
for any reason, you have an exception.

.. code-block:: python

   >>> T=CGNS.MAP.load('r.hdf')[0]
   >>> CGNS.MAP.save('r.hdf',T)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
     File "pyCHLone.pyx", line 159, in CHLone.save (pyCHLone.c:2718)
   CHLone.CHLoneException: (901, 'File already exists [r.hdf]')

   >>> CGNS.MAP.save('tmp/solution.cgns',T)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
     File "pyCHLone.pyx", line 169, in CHLone.save (pyCHLone.c:2918)
   CHLone.CHLoneException: (104, 'Cannot create new file [tmp/solution.cgns]')


Partial load of a *CGNS/HDF* file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

One of the big advantage of *CGNS* is to provide a complete and consistent
tree in data. Then you often want to parse it to get some information about
its structure or about a particuliar data. You do not want to load all
cooridnates and solution arrays, you just want the targeted information.
The *CGNS.MAP* load allows you to filter your target in the *CGNS/HDF5* file.

In this first example we load only the zone layout of the tree. You want 
to distribute your zones on a cluster processors and you need to now the number
of zones and their sizes::

  T=CGNS.MAP.load('z.hdf',depth=3)

The ``depth`` argument tells *CGNS.MAP* to stop loading at level 3, which is
the ``Zone_t`` level; The first level is the root of the tree (``CGNSTree_t``)
then you have the base level (``CGNSBase_t``) and the zones.

Note you have all nodes at level 3, not only the zones. You need to
select the zones::

  zlist=CGU.getAllNodesByTypeSet(T[0],['Zone_t'])

In the case you have several ``CGNSBase_t`` you may want to filter
a specific base, use the ``subtree`` to tell to ``load`` to select only
the nodes with the given prefix, all together this leads to the following
lines.

.. code-block:: python

   >>> T=CGNS.MAP.load('z.hdf',subtree='/Base_1',depth=3)
   >>> print CGU.getAllPaths(T[0])
   ['/Base_1', '/Base_1/About', '/Base_1/Zone_0001', '/Base_1/Zone_0002']
   >>> zlist=CGU.getAllNodesByTypeSet(T[0],['Zone_t'])

Load of a *CGNS/HDF* file layout
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The previous example actually reads all the data of the *CGNS/HDF5* file,
its stops parsing the tree but every node is retrieved with its associated
array. Now we would like to find out the coordinates names, the node names
of the children of ``GridCoordinates_t``, but we do not want to load a
probably large amount of data. We use the ``madata`` threshold parameter,
all data above this size is returned as a *no-data* node::

 flags=CGNS.MAP.S2P_DEFAULT|CGNS.MAP.S2P_NODATA
 T=CGNS.MAP.load('mesh.hdf',maxdata=20,flags=flags)

The ``CGNS.MAP.S2P_NODATA`` is required, without it the ``maxdata`` is
ignored. Here all the *CGNS/HDF5* tree is retrieved but nodes with a
data array with more than 20 entries is set as an ``MT`` (*empty*) node.
We can still parse the node names::

 >>> g=CGU.getNodeByPath(T[0],'/Mesh/Zone-004/GridCoordinates')
 ['GridCoordinates', None, [['CoordinateX', None, [], 'DataArray_t'], 
 ['CoordinateY', None, [], 'DataArray_t'], 
 ['CoordinateZ', None, [], 'DataArray_t']], 'GridCoordinates_t']

You should **not** save this tree, because you would overwrite the
coordinates nodes with empty arrays.

When you use the ``S2P_NODATA`` flag your result tree may contain ``MT``
nodes that actually have no data in the *CGNS/HDF5* file or node arrays that
have been ignored because of the ``maxdata`` threshold. The third entry
in the ``load`` return tuple is the ``paths``, it contains the paths of the
nodes that were ignored::

  >>> flags=CGNS.MAP.S2P_DEFAULT|CGNS.MAP.S2P_NODATA
  >>> T=CGNS.MAP.load('mesh.hdf',maxdata=20,flags=flags)
  >>> print T[2]
  [('/Mesh/Zone-004/GridCoordinates/CoordinateX',1),
   ('/Mesh/Zone-004/GridCoordinates/CoordinateY',1)
   ('/Mesh/Zone-004/GridCoordinates/CoordinateZ',1)]

The ``1`` associated with the path is the integer value corresponding to
the ``S2P_NODATA`` flag, it is useless at this time.

Update a nodata node from a *CGNS/HDF* file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now next step is to load the data for a node. Your first step was to parse the 
tree layout an discover the nodes you are intersted in. You have a list with
node paths taken from your previously loaded tree. Some nodes have data, because
their size was below the ``maxdata`` threshold, some other have ``None`` and
they appear in the ``paths`` as the third returned value of the load function.

First case, we want to load data from a ``None`` valued node. We use the
``subtree`` named argument of the load. It creates all the nodes of the
subtree starting from the path you give::

  flags=CGNS.MAP.S2P_DEFAULT|CGNS.MAP.S2P_NODATA
  (t1,l1,p1)=CGNS.MAP.load('mesh.hdf',maxdata=20,flags=flags)

  # this path is in p1
  pth='/Mesh/Zone-004/GridCoordinates/CoordinateX'
  (t2,l2,p2)=CGNS.MAP.load('mesh.hdf',subtree=pth)

  # replace the new node in the original tree
  nodatanode=CGU.getNodeByPath(t1,pth)
  datanode  =CGU.getNodeByPath(t2,pth)
  nodatanode[1]=datanode[1]

The numpy array is replaced in the list, python insures this is not a copy
and also takes care of the object references. So you can remore ``t2`` now.
In that case a new numpy array has been created. You can give an actual
subtree instead of a leaf, the result is the same, all children nodes are
recursively loaded::

  flags=CGNS.MAP.S2P_DEFAULT|CGNS.MAP.S2P_NODATA
  (t1,l1,p1)=CGNS.MAP.load('mesh.hdf'),maxdata=20,flags=CGNS.MAP.S2P_NODATA)

  pth='/Mesh/Zone-004/GridCoordinates'
  (t2,l2,p2)=CGNS.MAP.load('mesh.hdf',subtree=pth)

  # replace the new node in the original tree
  # here we change the children list instead of a single node value
  nodatanode=CGU.getNodeByPath(t1,pth)
  datanode  =CGU.getNodeByPath(t2,pth)
  nodatanode[2]=datanode[2]

Sometimes you just want a node that already exists to be updated. The
``update`` argument of the load is a dictionnary of path/value
entries::

  (t1,l1,p1)=CGNS.MAP.load('mesh.hdf')
  pth_g='/Mesh/Zone-004/GridCoordinates'
  pth_x=pth_g+'/CoordinateX'
  targetnode=CGU.getNodeByPath(t,pth_x)

  upd={ pth_x:targetnode[1] }
  (t2,l2,p2)=CGNS.MAP.load('mesh.hdf',update=upd,subtree=pth_g)

  # both t1 and t2 are sharing the /Mesh/Zone-004/GridCoordinates/CoordinateX
  # array, as the second load actullay updated the numpy array passed as arg
  # in the update

The previous example loads the X,Y and Z arrays in the ``t2`` tree. However,
only Y and Z had a new memory allocation. If you do not set the ``subtree`` arg
the ``t2`` tree will contain a completely new CGNS/Python, only the X coordinate
array is shared with ``t1``.

Another way to load the coordinates X,Y,Z having the X shared and Y and Z
new numpy array is to set ``None`` in the ``update`` dictionnary and to
add the ``S2P_UPDATEONLY`` flag. Only the paths into ``update`` would be
parsed in the CGNS/HDF5 file, if the ``update`` is ``None`` a new numpy array
is allocated, otherwise the existing numpy array found in the dictionnary
is updated::

  (t1,l1,p1)=CGNS.MAP.load('mesh.hdf')
  pth_g='/Mesh/Zone-004/GridCoordinates'
  pth_x=pth_g+'/CoordinateX'
  pth_y=pth_g+'/CoordinateY'
  pth_z=pth_g+'/CoordinateZ'
  targetnode=CGU.getNodeByPath(t,pth_x)

  upd={ pth_x:targetnode[1], pth_y:None, pth_z:None }

  flags=CGNS.MAP.S2P_DEFAULT|CGNS.MAP.S2P_UPDATEONLY
  (t2,l2,p2)=CGNS.MAP.load('mesh.hdf',update=upd,flags=flags)

We suggest you use this last method instead of calling as many load with as
many ``subtree`` you want to update. The CGNS/HDF5 file would be opened only
once, you increase parsing performance, you limit the file system access and
you reduce the critical section for other applications trying to read/write 
simultaneously the same file.

Load a tree without links
~~~~~~~~~~~~~~~~~~~~~~~~~

If you want to parse a CGNS/HDF5 file and avoid to load any linked-file found
during the parse, remove the ``S2P_FOLLOWLINKS`` from the flags::

  flags=CGNS.MAP.S2P_DEFAULT & ~CGNS.MAP.S2P_FOLLOWLINKS
  (t,l,p)=CGNS.MAP.load('mesh.hdf',flags=flags)

The ``l`` list is empty.

Save with links
~~~~~~~~~~~~~~~

When you load a file all the linked-to files areresolved to produce a full
CGNS/Python tree with actual node data. The only way to find out from which
file comes a specific node you have to check the link list, returned as the
second value of the load function. The way back, that is when you want to save
this previously loaded file, you have to give back this link list::

  (t,l,p)=CGNS.MAP.load('mesh.hdf')

  CGNS.MAP.save('mesh.hdf',t,links=l)

If you miss this ``links`` argument, the save will store all the nodes ot ``t``
in the file. This is a merge like describe :ref:`here <mergenolink>`.

The save will only write the first top file, all other sub parts of the 
tree having an ancestor in the links list would be ignored. If you want
the save modifications of nodes coming from a linked-to file, you have
to force the ``S2P_PROPAGATE`` flag::

  (t,l,p)=CGNS.MAP.load('mesh.hdf')

  flags=CGNS.MAP.S2P_DEFAULT|CGNS.MAP.S2P_PROPAGATE
  CGNS.MAP.save('mesh.hdf',t,links=l,flags=flags)

In that latter case, the linked-to file are set in the ``update`` mode as
described :ref:`here <updatemode>`.

Save with new links
~~~~~~~~~~~~~~~~~~~

*to add: example with recursive tree save per link*

.. _updatemode:

Update a file
~~~~~~~~~~~~~

*to add: example with recursive tree save per link*

.. _mergenolink:

Merge all or some linked-to files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

*to add: example with recursive tree save per link*

Some usual issues
~~~~~~~~~~~~~~~~~

*to do: add here users\' usual mistakes*


