
Le document de specification :
==============================

Globalement le document de specification correspond a
l'implementation qui a ete faite avec :

. Transport-ParaMEDMEM qui a ete enrichi avec la classe MPI_Access

. Presentation-ParaMEDMEM qui a ete enrichi avec la classe
  MPI_AccessDEC


La conception correspondant a cette specification est restee
la meme :

. MPI_Access gere pour un ProcessorGroup (IntraCommunicator) :
  - Les structures MPI_Request et MPI_Status
  - La valeur des "tags" MPI
  - Les requetes d'ecritures et de lectures asynchrones
  - Les communications en "Point a Point" [I]Send, [I]Recv ainsi
    que [I]SendRecv.
  - A la difference de l'API MPI [I]SendRecv ne concerne qu'un
    seul et meme "target".
  - Les controles de communications asynchrones Wait, Test,
    WaitAll, TestAll, [I]Probe, Cancel et CancelAll.
  - Comme c'etait demande seules les methodes "utiles" ont ete
    implementees.
  - Les appels a [I]Send ou a [I]Recv avec des sendbuff/recvbuff
    de valeur NULL ou avec des sendcount/recvcount de valeur
    nulle sont ignores.
  - Les methodes de communications collectives ne sont pas
    implementees dans MPI_Access.
  - Les deux methodes "Cancel" concernent soit un IRecv deja
    soumis soit un message en attente (sans IRecv deja soumis).
    Elles regroupent les differents appels de l'API MPI
    necessaires (IProbe, IRecv, Wait, Test_Canceled ...).

. MPI_AccessDEC utilise les services de MPI_Access pour un
  ProcessorGroup (IntraCommunicator) et gere :
  - Les communications collectives en "Point a Point".
    (AllToAll[v] synchrone ou asynchrone).
  - Les temps et l'interpolation
  - Les [I]Send avec leurs buffers (delete [])
  - Les [I]Recv
  - La finalisation des envois et receptions de messages dans
    le destructeur afin qu'il n'y ait plus de message en attente
    et afin de liberer les buffers


MPI_Access et "tags" (ou "MPITags") :
=====================================

. Le constructeur permet optionnellement de fixer une plage de tags
    a utiliser : [BaseTag , MaxTag].
  Par defaut c'est [ 0 , MPI_TAG_UB], MPI_TAG_UB etant la valeur
    maximum d'une implementation de MPI (valeur minimum 32767
    soit 2**15-1). Sur awa avec l'implementation lam MPI_TAG_UB
    vaut 7353944. La norme MPI specifie que cette valeur doit
    etre la meme dans les process demarres avec mpirun.
  Dans le cas de l'usage simultane du meme IntraCommunicator
    dans un meme process (ou de plusieurs IntraCommunicator
    d'intersection non nulle) cela peut eviter toute ambiguite
    et aider au debug.

. Dans MPI_Access les tags sont constitues de deux parties
    (#define ModuloTag 10) :
  + Le dernier digit decimal correspond au MPI_DataType ( 1 pour
    les messages "temps", 2 pour MPI_INT et 3 pour MPI_DOUBLE)
  + La valeur des autres digits correspond a une numerotation
    circulaire des messages.
  + Un message "temps" et le message de donnees associe ont le
    meme numero de message (mais des types et donc des tags
    differents).

. Pour un envoi de message d'un process "source" vers un process
    "target", on dispose de _SendMPITag[target] dans le process
    source (il contient le dernier "tag" utilise pour l'envoi de
    messages vers le process target).
  Et dans le process "target" qui recoit ce message, on dispose
    de _RecvMPITag[source] (il contient le dernier "tag" utilise
    pour la reception de messages du process source).
  Naturellement d'apres la norme MPI les valeurs de ces tags sont
    les memes.


MPI_Access et "RequestIds" :
============================

. ATTENTION : Dans le document de specification, la distinction
  n'est pas faite clairement entre les "MPITags" (voir ci-dessus)
  qui sont un argument des appels a MPI et les "RequestIds" qui
  ne concernent pas les appels MPI. Ces "RequestIds" figurent
  en effet sous le nom de tag comme argument d'entree/sortie dans l'API
  de MPI_Access decrite dans le document de specification. Mais
  dans l'implementation on a bien le nom RequestId (ou bien
  RecvRequestId/SendRequestId).

. Lors de la soumission d'une requete d'ecriture ou de lecture MPI
    via MPI_Access, on obtient un identifieur "RequestId".
  Cet identifieur "RequestId" correspond a une structure RequestStruct
    de MPI_Access a laquelle on accede avec la map
    "_MapOfRequestStruct".
  Cette structure RequestStruct permet de gerer MPI_Request et
    MPI_Status * de MPI et permet d'obtenir des informations sur
    la requete : target, send/recv, tag, [a]synchrone, type, outcount.

. C'est cet identifieur qui peut etre utilise pour controler une
    requete asynchrone via MPI_Access : Wait, Test, Probe, etc...

. En pratique "RequestId" est simplement un entier de l'intervalle
    [0 , 2**32-1]. Il y a uniquement un compteur cyclique global
    aussi bien pour les [I]Send que pour les [I]Recv.

. Ces "RequestIds" et leur structures associees facilitent les
    communications asynchrones.
  Par exemple on a mpi_access->Wait( int RequestId )
  au lieu de MPI_Wait(MPI_Request *request, MPI_Status *status)
  avec gestion de status.

. L'API de MPI_Access peut fournir les "SendRequestIds" d'un "target",
    les "RecvRequestIds" d'un "source" ou bien les "SendRequestIds" de
    tous les "targets" ou les "RecvRequestIds" de tous les "sources".
  Cela permet d'eviter leur gestion au niveau de Presentation-ParaMEDMEM.


MPI_AccessDEC :
===============

. Comme la classe DEC, il est base sur local_group et distant_group
  ce qui forme un MPI_union_group et donc un IntraCommunicator.

. Il permet de choisir le mode synchrone ou asynchrone (par defaut).
  Le meme programme peut fonctionner en synchrone ou en asynchrone
  sans devoir etre modifie.

. Il permet de choisir un mode d'interpolation (actuellement
  uniquement une interpolation lineaire) ou bien un mode sans
  interpolation (par defaut). Ceci pour les communications collectives.
  Avec interpolation les communications collectives transmettent et
  recoivent un message "temps" en plus des donnees.

. Il implemente AllToAll[v] en "Point a Point" avec ou sans interpolation.

. Il gere les buffers d'envoi de messages. Il les detruit donc
  lorsqu'ils sont disponibles.

. Il cree et utilise MPI_Access.


MPI_AccessDEC et la gestion des SendBuffers :
=============================================

. Comme dans les communications collectives on n'envoie que des
  parties du meme buffer  chaque process "target", il faut s'assurer
  en asynchrone que toutes ces parties sont disponibles pour
  pouvoir liberer le buffer.

. On suppose que ces buffers ont ete alloues avec un new double[]

. La structure SendBuffStruct permet de conserver l'adresse du buffer
  et de gerer un compteur de references de ce buffer. Elle comporte
  aussi MPI_Datatype pour pouvoir faire un delete [] (double *) ...
  lorsque le compteur est null.

. La map _MapOfSendBuffers etablit la correspondance entre chaque
  RequestId obtenu de MPI_Access->ISend(...) et un SendBuffStruct
  pour chaque "target" d'une partie du buffer.

. Tout cela ne concerne que les envois asynchrones. En synchrone,
  on detruit senbuf juste apres l'avoir transmis.


MPI_AccessDEC et la gestion des RecvBuffers :
=============================================

S'il n'y a pas d'interpolation, rien de particulier n'est fait.

Avec interpolation pour chaque target :
---------------------------------------
. On a _TimeMessages[target] qui est un vecteur de TimesMessages.
  On en a 2 dans notre cas avec une interpolation lineaire qui
  contiennent le time(t0)/deltatime precedent et le dernier
  time(t1)/deltatime.

. On a _DataMessages[target] qui est un vecteur de DatasMessages
  On en a 2 dans notre cas avec une interpolation lineaire qui
  contiennent les donnees obtenues par Recv au time(t0)/deltatime
  precedent et au dernier time(t1)/deltatime.

. Au temps _t(t*) du processus courrant on effectue l'interpolation
  entre les valeurs des 2 DatasMessages que l'on rend dans la
  partie de recvbuf correspondant au target pourvu que t0 < t* <= t1.

. Par suite de la difference des "deltatimes" entre process, on
  peut avoir t0 < t1 < t* auquel cas on aura une extrapolation.

. Les vecteurs _OutOfTime, _DataMessagesRecvCount et _DataMessagesType
  contiennent pour chaque target true si t* > dernier t1, recvcount et
  MPI_Datatype pour finaliser la gestion des messages a la fin.


Etapes des communications collectives de MPI_AccessDEC :
========================================================

AllToAll[v] : Les arguments sont les memes que dans MPI sauf MPI_Comm
------------- inutile (deja connu de MPI_AccessDEC et MPI_Access).

              Si on a un TimeInterpolator, appel de AllToAll[v]Time.

              Sinon, on appelle CheckSent pour les echanges
                asynchrones (voir ci-apres) et on appelle SendRecv
                pour chaque "target".

AllToAll[v]Time : 
-----------------

. CheckSent() :
  + appelle SendRequestIds de MPI_Access afin d'obtenir tous les
    RequestIds d'envoi de messages a tous les "targets".
  + Pour chaque RequestId, appelle Test de MPI_Access pour savoir
    si le buffer est libre (flag = true). Lorsqu'il s'agit du
    FinalCheckSent, on appelle Wait au lieu de Test.
  + Si le buffer est libre, on decremente le compteur de la
    structure SendBuffStruct obtenue avec _MapOfSendBuffers.
    (voir MPI_AccessDEC et la gestion des SendBuffers ci-dessus)
  + Si le compteur est nul on detruit le TimeMessage ou le
    SendBuffer en fonction du DataType.
  + Puis on detruit la structure SendBuffStruct avant de supprimer
    (erase) cet item de _MapOfSendBuffers

. DoSend :
  + On cree un TimeMessage (voir cette structure dans MPI_Access).
  + Si l'on est en asynchrone on cree deux structures SendBuffStruct
    aSendTimeStruct et aSendDataStruct que l'on remplit.
  + On remplit la structure aSendTimeMessage avec time/deltatime du
    process courant. "deltatime" doit etre nul s'il s'agit du dernier
    pas de temps.
  + Puis pour chaque "target", on envoie le TimeMessage et la partie
    de sendbuf concernee par ce target.
  + Si l'on est en asynchrone, on incremente le compteur et on ajoute
    a _MapOfSendBuffers aSendTimeStruct et aSendDataStruct avec les
    identifieurs SendTimeRequestId et SendDataRequestId recus de
    MPI_Access->Send(...).
  + Et enfin si l'on est en synchrone, on detruit les SendMessages.

. CheckTime(recvcount , recvtype , target , UntilEnd)
  + Au depart, on lit le premier "Message-temps" dans
    &(*_TimeMessages)[target][1] et le premier message de donnees
    dans le buffer alloue (*_DataMessages)[target][1].
  + Par convention deltatime des messages temps est nul si c'est le
    dernier.
  + Boucle while : _t(t*) est le temps courant du processus.
    "tant que _t(t*) est superieur au temps du "target"
     (*_TimeMessages)[target][1].time et que
     (*_TimeMessages)[target][1].deltatime n'est pas nul",
    ainsi en fin de boucle on aura :
     _t(t*) <= (*_TimeMessages)[target][1].time avec
     _t(t*) > (*_TimeMessages)[target][0].time
    ou bien on aura le dernier message temps du "target".
  + S'il s'agit de la finalisation des receptions des messages
    temps et donnees (UntilEnd vaut true), on effectue la
    boucle jusqu'a ce que l'on trouve
    (*_TimeMessages)[target][1].deltatime nul.
  + Dans la boucle :
    On recopie le dernier message temps dans le message temps
      precedent et on lit le message temps suivant.
    On detruit le buffer de donnees du temps precedent.
    On recopie le pointeur du dernier buffer de donnees dans
      le precedent.
    On alloue un nouveau dernier buffer de donnees
      (*_DataMessages)[target][1] et on lit les donnees
      correspondantes dans ce buffer.
  + Si le temps courant du process est plus grand que le dernier
    temps (*_TimeMessages)[target][1].time du target, on donne
    la valeur true a (*_OutOfTime)[target].
    (*_TimeMessages)[target][1].deltatime est alors nul.

. CheckTime + DoRecv + DoInterp
  + Pour chaque target on appelle CheckTime
  + Si on a un TimeInterpolator et si le message temps du target
    n'est pas le premier, on appelle l'interpolateur qui stocke
    ses resultats dans la partie du buffer de reception qui
    correspond au "target".
  + Sinon, on recopie les donnees recues pour ce premier pas de
    temps dans la partie du buffer de reception qui correspond au
    "target".


Presentation-ParaMEDMEM :
=========================

. Des modifications mineures ont ete effectuees dans Presentation-ParaMEDMEM
  afin de pouvoir utiliser ces nouvelles fonctionnalites. Il n'y
  a surtout pas eu de bouleversement destabilisateur. L'ancien
  mode de fonctionnement reste naturellement disponible.

. Cela repose sur trois nouvelles options creees avec registerOption
  dans le constructeur de InterpKernelDEC :
  + Asynchronous : true ou false (par defaut)
  + TimeInterpolation : WithoutTimeInterp (par defaut) ou LinearTimeInterp
    typedef enum{WithoutTimeInterp,LinearTimeInterp} TimeInterpolationMethod;
    dans MPI_AccessDEC.hxx
  + AllToAllMethod : Native (par defaut) ou PointToPoint
    typedef enum{Native,PointToPoint} AllToAllMethod;
    dans MxN_Mapping.hxx

. Le choix des options se fait avec le Data Exchange Channel :
  + ParaMEDMEM::InterpKernelDEC dec (*source_group,*target_group);
  + dec.setOption("Asynchronous",true);
  + dec.setOption("TimeInterpolation",LinearTimeInterp);
  + dec.setOption("AllToAllMethod",PointToPoint);

. Dans dec.synchronize(),
  + on cree un objet InterpolationMatrix
    qui lui-meme cree un objet MxN_Mapping
    qui lui-meme cree maintenant un objet MPI_AccessDEC
  + on transmet a MxN_Mapping via l'InterpolationMatrix l'option
    choisie de AllToAllMethod
  + on transmet a MPI_AccessDEC les valeurs des options Asynchronous
    et TimeInterpolation : methodes Asynchronous et
    SetTimeInterpolator de MPI_AccessDEC.

. ParaMEDMEM::InterpKernelDEC comporte maintenant une surcharge des
  methodes recvData() et sendData() :
  + void InterpKernelDEC::recvData( double time ) qui appelle
    SetTime(time) de MPI_AccessDEC et
    recvData()
  + void InterpKernelDEC::sendData( double time , double deltatime )
    qui appelle 
    SetTime(time,deltatime) de MPI_AccessDEC et
    sendData()

. recvData() et sendData() de ParaMEDMEM::InterpKernelDEC
  appellent multiply et transposeMultiply de l'InterpolationMatrix
  qui appellent sendRecv et reverseSendRecv de MxN_Mapping
  qui appellent comm_interface.allToAllV en mode "Native"
  ou bien MPI_AccessDEC::AllToAllv en mode "PointToPoint"

