GEMS3K Solver of GeoChemical Equilibria

Coupling with GEMS3K at Simple TNode Level


Contents

1. Introduction

2. Data exchange using disk files only

3. Data exchange in computer memory

4. Frequently asked questions



1. Introduction

The  main.cpp  file in "standalone/gemcalc/" folder shows how to perform a batch calculation of chemical equilibria using the input data given in DCH, IPM and one or more DBR files; the results are written into DBR files or (optionally) into a dump text output file. 

Main.cpp and main.h  files in  "standalone/node-gem/" folder  shows how the coupling with a "transport part" having several nodes can be arranged, where the chemical data and parameters are kept in dynamic arrays and exchanged with the GEMS3K in computer memory. The modification to a “real” mass transport method is straightforward, although this would make the example program too complicated. 

In these examples, some options are shown that may significantly improve performance and accuracy of GEMS3K. 

As the mass transport part has no direct access to internal IPM work data, the calculations of equilibria occur as if they were done in “real” amounts consistent with the current size of the node. The use of internal re-scaling mechanism leads, in general, to a much better convergence of GEMS3K and to higher precision of its results.

The current chemical speciation (xDC vector, amounts of Dependent Components) is usually the output from GEM IPM runs (can also be used as input initial approximation in SIA mode). The ultimate input of GEMS3K is the bulk chemical composition vector (bIC), temperature and pressure. So, at time steps, the mass transport part should change the bIC vector and, maybe, T and/or P in every node, and this must be sufficient.    

However, in many existing advection/diffusion transport codes (e.g. OpenGeoSys), not the total amounts of independent components, but rather the amounts of aqueous species are moved between nodes. Conversely, after the mass transport step, only the speciation vector xDC is changed in each node, and an additional step would be needed to compute a corrected bIC vector (i.e. elemental bulk composition) in the mass transport part, which will be the GEM input for computing the new speciation xDC.  In turn, this would require access to several data objects (such as the A stoichiometry matrix) in the DATACH structure.

In order to make this unnecessary, the TNode class provides a variant of GEM_from_MT() function call where the re-calculation of input (changed) xDC speciation into new bIC bulk composition vector is performed inside of GEMS3K before the internal mass scaling and calculation of new equilibrium state. Actually, total amounts of Independent Components are calculated from stoichiometries of Dependent Components (as provided in the A matrix) and their amounts (in the xDC vector passed through the p_xDC parameter), and then added to the amounts given in the bIC vector (passed through p_bIC parameter). Here, two cases are possible. One case is when the mass transport step puts in the p_xDC parameter the differences between the new and the old speciation in the node chemical system. In this case, the old p_bIC content (e.g. unchanged since the last GEM calculation for this node) must be provided and it will be automatically corrected.  Another (more usual) case is when the mass-transport part delivers in p_xDC the complete new speciation (absolute amounts) after the transport time step. In this case, the p_bIC vector must be filled with zeros because its new content will be completely retrieved from the p_xDC vector.

Concerning the “Smart Initial Approximation” (SIA) mode, the TNode class contains a parameter in GEM_run() call and yet another variant of GEM_from_MT() function that control what initial approximation of GEM speciation vector will be used. Basically, regardless of what the mass transport part does, the GEMS3K can use previous contents of speciation and activity coefficients vectors in MULTI structure as an initial approximation for the next GEM calculation – even if the bulk composition b or temperature T have had changed. This mode is set by the default uPrimalSol parameter value (false).

Another, more efficient variant (uPrimalSol = true) can be used when the mass transport part directly changes the chemical speciation in the node while taking care of material balance, and the bIC input vector must be restored anyway from the new xDC vector contents (provided together with activity coefficients stored for this node at previous time step). In this case, the SIA mode is very efficient because the mass balance is already ensured, and this may lead to a very serious reduction of the number of GEM iterations and total computing time.  Note that this SIA mode works only when the DATACH structure contains a full list of Dependent Components used in GEM calculations.

The TNode class provides dozens of access methods to single items of chemical data. Usage of these public methods is strongly preferred over direct access to fields of the DATABR and DATACH memory structures. The provided methods use either DATABR or DATACH indexation.

In DBR files and DATABR structure, the results for volumes, masses and bulk compositions are only provided for multi-component phases, because for pure phases they can be easily retrieved. Nevertheless, TNode public functions such as Ph_Volume(), Ph_Mass(), Ph_BC() and Ph_SatInd() work both for multi- and single-component phases. This is done in order to make access to such data from the side of mass-transport part of the program as simple as possible.


2. Data exchange using disk files only

Programming example fragment (see full example in "standalone/gemcalc/main.cpp"):

#include "node.h"
  
int main( int argc, char* argv[] )
 {
   long nRecipes = 0;
   char (*recipes)[fileNameLength] = 0;
  
   // Analyzing command line arguments
   // Default arguments
   char input_system_file_list_name[256] = "system-dat.lst";
   char input_recipes_file_list_name[256] = "more_recipes.lst";
  
   if (argc >= 2 )
       strncpy( input_system_file_list_name, argv[1], 256);
   // list of DCH, IPM and DBR input files for initializing GEMS3K
  
   // Creates TNode structure instance accessible trough the "node" pointer
   TNode* node  = new TNode();
 
   // (1) Initialization of GEMS3K internal data by reading  files
   //     whose names are given in the input_system_file_list_name
   if( node->GEM_init( input_system_file_list_name ) )
   {
      // error occured during reading the files
      return 1;
   }

   // Getting direct access to work node DATABR structure which exchanges the
   // data with GEM IPM3 (already filled out by reading the DBR input file)
   DATABR* dBR = node->pCNode();


// . . . . . . . . . . . . . . .

   // Asking GEM to run with automatic initial approximation
   dBR->NodeStatusCH = NEED_GEM_AIA;

   // (2) re-calculating equilibrium by calling GEMS3K, getting the status back
   int NodeStatusCH = node->GEM_run( false );

   if( NodeStatusCH == OK_GEM_AIA || NodeStatusCH == OK_GEM_SIA  )
   {    // (3) Writing results in default DBR file
       node->GEM_write_dbr( NULL, false, true, false );
//       node->GEM_print_ipm( NULL );   // possible debugging printout
   }
   else {
      // (4) possible return status analysis, error message
       node->GEM_print_ipm( NULL );   // possible debugging printout
       return 5; // GEM IPM did not converge properly - error message needed
   }

// . . . . . . . . . . . . . . .

   // Here a possible loop on more input recipes begins
   if (argc >= 3 )
   { 
       char NextRecipeFileName[256];
       char NextRecipeOutFileName[300];
       char input_recipes_file_list_path[256-fileNameLength] = "";
    
      strncpy( input_recipes_file_list_name, argv[2], 256);
      // list of additional recipes (dbr.dat files) e.g. for simulation
      // of a titration or another irreversible process
        // Reading list of recipes names from file
      recipes = f_getfiles(  input_recipes_file_list_name,
                      input_recipes_file_list_path, nRecipes, ',');             
      for(int cRecipe=0; cRecipe < nRecipes; cRecipe++ )
      {
         // Trying to read the next file name
          sprintf(NextRecipeFileName , "%s%s", input_recipes_file_list_path, recipes[cRecipe] );

        // (5) Reading the next DBR file with different input composition or temperature
        node->GEM_read_dbr( NextRecipeFileName );

        // Asking GEM IPM2 to run (faster) with smart initial approximation
        dBR->NodeStatusCH = NEED_GEM_SIA;      

        NodeStatusCH = node->GEM_run( false );

        if( NodeStatusCH == OK_GEM_AIA || NodeStatusCH == OK_GEM_SIA  )
        {    sprintf(NextRecipeOutFileName , "%s.out", NextRecipeFileName );
             node->GEM_write_dbr( NextRecipeOutFileName, false, false, false );
             sprintf(NextRecipeOutFileName , "%s.Dump.out", NextRecipeFileName );
             node->GEM_print_ipm( NextRecipeOutFileName );
        }
        else {
               // error message, debugging printout
               sprintf(NextRecipeOutFileName , "%s.Dump.out", NextRecipeFileName );
              node->GEM_print_ipm( NextRecipeOutFileName );
//              return 5; // GEM IPM did not converge properly - error message needed
              }
      }
   }     
   // end of possible loop on input recipes
   delete node;
   if( recipes ) delete recipes;

 // End of example 
   return 0;


In this example, the GEMS3K input file names are provided in two .lst text files, whose paths are given as command line arguments. The second list (passed when argc >= 3) may contain one or many DBR input files (for systems at different T, P, or bulk composition). These files are processed in a loop; the function f_getfiles also counts how many file names are in the .lst files. The input DBR files must be provided (e.g. exported from GEM-Selektor or cloned/modified using a text editor) in the same folder as the .lst files.


3. Data exchange in computer memory

This case is typical for coupling GEMS3K with an existing reactive mass transport code that has its own data structure for keeping chemical information (e.g. speciation, bulk composition of the system) for each node. As in the previous example, it uses one work instance of the DATABR memory structure as data bridge for the exchange with internal GEM IPM data structures.

This example code consists of two C++ files: "main.h" and "main.cpp", located in the "standalone/node-gem/" folder. The former file contains a definition of TMyTransport class representing a typical dynamic memory structure used for keeping current chemical composition and speciation for all nodes involved in the reactive mass transport system. The latter file contains an implementation of some public methods of TMyTransport class, as well as the main function which shows the sequence of calls to TNode class functions, necessary to perform coupled reactive transport calculations based on the "operator splitting" approach.

This example is designed to help learning how to couple the existing transport code with GEMS3K in a simple way, and it does not contain a 'real' transport code which would make the example more difficult to observe. The transport model itself is located in the TMyTransport::OneTimeStepRun() function and can be improved there to any desired extent.

Here is a code snippet with the TMyTransport class definition (see complete code in standalone/node-gem/main.h):

#include "node.h"

class TMyTransport
{
 public:
    long int nNodes,  // Number of mass transport nodes
            nTimes,   // Number of time steps
            nIC,      // Number of chemical independent components
            nDC,      // Number of chemical dependent components
            nPH,      // Number of chemical phases
            nPS,      // Number of chemical phases-solutions
            nRecipes; // Number of different input node recipes to set boundary conditions

    long int *aNodeHandle,     // Node identification handles
             *aNodeStatusCH,   // Node status codes (changed after GEM calculation)
             *aIterDone;       // Number of GEM IPM iterations performed for each node
                               //   at the last time step

    double *aT,     // Array of node temperatures T, Kelvin
           *aP,     // Array of node pressures P, Pa
           *aVs,    // Array of node volume V of reactive subsystem, m3
           *aMs,    // Array of node mass of reactive subsystem, kg
           *aGs,    // Array of node total Gibbs energy of reactive subsystems, J
           *aHs,    // Array of node total enthalpy of reactive subsystems, J (reserved)
           *aIC,    // Array of node effective aqueous ionic strengths, molal
           *apH,    // Array of node pH of aqueous solutions
           *ape,    // Array of node pe of aqueous solutions
           *aEh;    // Array of node Eh of aqueous solution, V

    double **axDC,  // Array of node mole amounts of dependent components (speciation)
           **agam,  // Array of node activity coefficients of dependent components
           **axPH,  // Array of node total mole amounts of all reactive phases
           **aaPH,  // Array of node specific surface areas of phases, m2/kg
           **avPS,  // Array of node total volumes of multicomponent phases, m3
           **amPS,  // Array of node total masses of multicomponent phases,kg
           **abPS,  // Array of node bulk compositions of multicomponent phases, moles
           **axPA,  // Array of node amount of carrier in asymmetric phases, moles
           **aaPh,  // Array of node surface areas of phases, m2
           **adul,  // Array of node upper restrictions to amounts of dependent components
           **adll,  // Array of node lower restrictions to amounts of dependent components
           **abIC,  // Array of node bulk mole amounts of independent components
           **arMB,  // Array of node mole balance residuals for independent components
           **auIC,  // Array of node chemical potentials of independent components (norm.)
           **abSP;  // Array for bulk composition of solid part of equilibrated sub-system

        TMyTransport(   // Constructor (dynamic memory allocation)
           long int p_nNod,    // Number of nodes
           long int p_nTim,    // Number of time steps
           long int p_nIC,     // Number of chemical independent components
           long int p_nDC,     // Number of chemical dependent components
           long int p_nPH,     // Number of chemical phases
           long int p_nPS,     // Number of chemical phases - solutions
           long int p_nRcps    // Number of different input node recipes to set boundary conditions
                );

        ~TMyTransport();  // Destructor of dynamic memory

         void OneTimeStepRun(   // Placeholder function for one transport time step
            double *stoich,     // Stoichiometry coefficients
            long int *ICndx,    // Indexes of mobile independent components
            long int nICndx     // Number of mobile independent components
                 );
};


All data and methods in this class are declared as 'public' to make the example program more straightforward. The three functions belonging to TMyTransport class are implemented in the "standalone/node-gem/main.cpp" file as follows:

#include "node.h"
#include "main.h"

TMyTransport::TMyTransport( long int p_nNod, long int p_nTim, long int p_nIC, long int p_nDC,
              long int p_nPH, long int p_nPS, long int p_nRcps )
{

    nNodes = p_nNod;
    nTimes = p_nTim;
    nIC = p_nIC;
    nDC = p_nDC;
    nPH = p_nPH;
    nPS = p_nPS;
    nRecipes = p_nRcps;

    aNodeHandle = new long int [nNodes];
    aNodeStatusCH = new long int [nNodes];
    aIterDone = new long int [nNodes];

    aT = new double [nNodes];
    aP = new double [nNodes];
    aVs = new double [nNodes];
    aMs = new double [nNodes];
    aGs = new double [nNodes];
    aHs = new double [nNodes];
    aIC = new double [nNodes];
    apH = new double [nNodes];
    ape = new double [nNodes];
    aEh = new double [nNodes];

    axDC = new double *[nNodes];
    agam = new double *[nNodes];
    axPH = new double *[nNodes];
    aaPH = new double *[nNodes];
    avPS = new double *[nNodes];
    amPS = new double *[nNodes];
    abPS = new double *[nNodes];
    axPA = new double *[nNodes];
    aaPh = new double *[nNodes];
    adul = new double *[nNodes];
    adll = new double *[nNodes];
    abIC = new double *[nNodes];
    arMB = new double *[nNodes];
    auIC = new double *[nNodes];
    abSP = new double *[nNodes];

    for (long int in=0; in<nNodes; in++)
    {
         abIC[in] = new double [nIC];
         arMB[in] = new double [nIC];
         auIC[in] = new double [nIC];
         axDC[in] = new double [nDC];
         agam[in] = new double [nDC];
         adul[in] = new double [nDC];
         adll[in] = new double [nDC];
         aaPH[in] = new double [nPH];
         axPH[in] = new double [nPH];
         avPS[in] = new double [nPS];
         amPS[in] = new double [nPS];
         axPA[in] = new double [nPS];
         aaPh[in] = new double [nPH];
         abPS[in] = new double [nIC*nPS];
         abSP[in] = new double [nIC];
    }
}


TMyTransport::~TMyTransport()
{
    // Deleting chemical data arrays for nodes
    for (long int in=0; in<nNodes; in++)
    {
        delete[]abIC[in];
        delete[]arMB[in];
        delete[]auIC[in];
        delete[]axDC[in];
        delete[]agam[in];
        delete[]adul[in];
        delete[]adll[in];
        delete[]aaPH[in];
        delete[]axPH[in];
        delete[]avPS[in];
        delete[]amPS[in];
        delete[]axPA[in];
        delete[]aaPh[in];
        delete[]abPS[in];
        delete[]abSP[in];
    }
    delete[]axDC;
    delete[]agam;
    delete[]axPH;
    delete[]aaPH;
    delete[]avPS;
    delete[]amPS;
    delete[]abPS;
    delete[]axPA;
    delete[]aaPh;
    delete[]adul;
    delete[]adll;
    delete[]abIC;
    delete[]arMB;
    delete[]auIC;
    delete[]abSP;

    delete[]aNodeHandle;
    delete[]aNodeStatusCH;
    delete[]aIterDone;
    delete[]aT;
    delete[]aP;
    delete[]aVs;
    delete[]aMs;
    delete[]aGs;
    delete[]aHs;
    delete[]aIC;
    delete[]apH;
    delete[]ape;
    delete[]aEh;
}

// A very simple example of transport algorithm
void TMyTransport::OneTimeStepRun( double *stoicf, long int *ICndx, long int nICndx )
{
    double parcel[nICndx];
    long int in;
    for(  in=1; in< nNodes; in++ )
    {
    // some operators that change in some nodes some amounts of some migrating
    // chemical species (axDC array or  some amounts of migrating chemical
    // elements in abIC array), possibly using data from other arrays in such
    // a way that the mass conservation within the whole array of nodes is retained
        for (int i=0; i<nICndx; i++)
        {
            parcel[i] = 0.01* stoicf[i]*abIC[in-1][ICndx[i]];
            abIC[in][ICndx[i]] += parcel[i];
//            if( in > 1 )
//                abIC[in-1][ICndx[i]] -= parcel[i];
        }
    // The above example loop implements a zero-order flux of MgCl2 in one direction.
    // Real advective/diffusive transport models are much more complex, but essentially
    //   do similar things
    }
}


The last function OneTimeStepRun() is not an implementation of a 'real' mass transport integration time step, but otherwise, it does the same, i.e. modifies bulk chemical composition of nodes by shifting some amounts of elements from one node to another, using the zeroth node as an infinite source of constant composition - MgCl2 solution. And it is called from the same place of the example coupled code where the code performing the transport time step should be called from (see below).
  
 
The first part of main.cpp (see full example in "standalone/node-gem/main.cpp") creates the TNode class instance and initializes it by reading three GEMS3K input files (provided in "standalone/node-gem-build/tp_test/"), which it extracts from the file list passed through the first command line argument. Then an instance of the TMyTransport class is created, and its constructor is called to allocate dynamic memory arrays for storing information for all nodes. If necessary, another instance of TMyTransport class could be created and initialized here, for example, if the state of the system at previous time step is needed to calculate mass transport at the current time step.


#include "node.h"
#include "main.h"

//The case of data exchange in computer memory
int main( int argc, char* argv[] )
{
   char (*recipes)[fileNameLength] = 0;

   // Analyzing command line arguments ( Default arguments)
   char input_system_file_list_name[256] = "system-dat.lst";
   char input_recipes_file_list_name[256] = "more_recipes.lst";

   if (argc >= 2 )
       strncpy( input_system_file_list_name, argv[1], 256);
   // list of DCH, IPM and DBR input files for initializing GEMS3K
   if (argc >= 3 ) // list of DBR files for setting boundary conditions
       strncpy( input_recipes_file_list_name, argv[2], 256);

    // Creates TNode structure instance accessible trough the "node" pointer
    TNode* node  = new TNode();

    // (1) Initialization of GEMS3K internal data by reading  files
    //     whose names are given in the ipm_input_system_file_list_name
    if( node->GEM_init( input_system_file_list_name ) )
    {
          cout << "Error occured during reading the files" ;
          return 1;
    }

    // Getting direct access to work node DATABR structure which exchanges the
    // data with GEMS3K (already filled out by reading the DBR input file)
    DATABR* dBR = node->pCNode();

    // Getting direct access to DataCH structure in GEMS3K instance memory
    DATACH* dCH = node->pCSD();

    // Creating memory for mass transport nodes
    // 11 nodes, 99 time steps
    TMyTransport mt( 11, 100, dCH->nICb, dCH->nDCb, dCH->nPHb, dCH->nPSb, 1 );



The next part of the example sets the initial state of the mass transport system by taking the data for the chemical system from GEMS3K. These data were read in upon initialization of the TNode class instance (see above) and are copied into all nodes but the zeroth one. After the GEM calculation for each node, the node status code is checked in order to assess quality of GEM solution. Initialization of nodes is done in the same loop with GEM calculations, even though the bulk composition of the system is constant. This is because, in general, temperature T and pressure P may change between the nodes, hence the chemical speciation must be made consistent by recalculating chemical equilibrium at the respective T and P. In real transport codes, this part - setting initial conditions and composition in all nodes - is much more complex.

To set boundary conditions, different chemical system recipes are read separately in a GEM_read_dbr() call taking DBR file names from the file list given as the second command line argument). Then, after calculation of equilibrium at desired T,P, the data is copied into mass transport arrays for the node with the respective index. In the particular case of this example, there is only one boundary condition for the node with index 0 that has a different composition and will be used by the transport part as a constant infinite source of MgCl2, moved into other nodes (containing calcium carbonate) by mass transport over time steps.

   
    // Initialization of GEMS3K and chemical information for nodes kept in the MT part
    long int in;
    for(  in=0; in< mt.nNodes; in++ )
    {
        // Asking GEM IPM to run with automatic initial approximation
        dBR->NodeStatusCH = NEED_GEM_AIA;
        // (2) re-calculating equilibrium by calling GEMIPM2K, getting the status back
        mt.aNodeStatusCH[in] = node->GEM_run( false);
        if( !( mt.aNodeStatusCH[in] == OK_GEM_AIA || mt.aNodeStatusCH[in] == OK_GEM_SIA ) )
        {
              cout << "Error occured during re-calculating equilibrium" ;
              return 5;
        }

        // Extracting GEM IPM input data to mass-transport program arrays
        node->GEM_restore_MT( mt.aNodeHandle[in], mt.aNodeStatusCH[in], mt.aT[in], mt.aP[in],
            mt.aVs[in], mt.aMs[in], mt.abIC[in], mt.adul[in], mt.adll[in], mt.aaPH[in] );
          
        // Extracting GEM IPM output data to mass-transport program arrays
        node->GEM_to_MT( mt.aNodeHandle[in], mt.aNodeStatusCH[in], mt.aIterDone[in],
            mt.aVs[in], mt.aMs[in], mt.aGs[in], mt.aHs[in], mt.aIC[in], mt.apH[in], mt.ape[in],
            mt.aEh[in], mt.arMB[in], mt.auIC[in], mt.axDC[in], mt.agam[in], mt.axPH[in],
            mt.avPS[in], mt.amPS[in], mt.abPS[in], mt.axPA[in], mt.aaPh[in], mt.abSP[in] );

        // Here the setup of initial differences between node compositions,
        //    temperatures, etc. can be implemented
        //
        // Here the file output for the initial conditions can be implemented
    }

    if (argc >= 3 )  // Read DATABR structure from text file
    {
       char NextRecipeFileName[256];
       char NextRecipeOutFileName[300];
       char input_recipes_file_list_path[256-fileNameLength] = "";

       // Reading list of recipes names from file
       recipes = f_getfiles(  input_recipes_file_list_name,
                  input_recipes_file_list_path, mt.nRecipes, ',');
       // in this example, nRecipes = 1 (one additional input recipe)

       for(  in=0; in<min(mt.nRecipes, mt.nNodes); in++ )
       {
          // Trying to read the next DBR file name
          sprintf(NextRecipeFileName , "%s%s", input_recipes_file_list_path, recipes[in] );

          // (5) Reading the next DBR file (boundary condition on the left)
          node->GEM_read_dbr( NextRecipeFileName );
          // Asking GEM IPM to run with automatic initial approximation
          dBR->NodeStatusCH = NEED_GEM_AIA;
          // (2) Re-calculating chemical equilibrium by calling GEM
          mt.aNodeStatusCH[in] = node->GEM_run( false );
          if( !( mt.aNodeStatusCH[in] == OK_GEM_AIA || mt.aNodeStatusCH[in] == OK_GEM_SIA ) )
          {
              cout << "Error occured during re-calculating chemical equilibrium" ;
              return 5;
          }

          // (6) Extracting GEMIPM input data to mass-transport program arrays
          node->GEM_restore_MT( mt.aNodeHandle[in], mt.aNodeStatusCH[in], mt.aT[in], mt.aP[in],
              mt.aVs[in], mt.aMs[in], mt.abIC[in], mt.adul[in], mt.adll[in], mt.aaPH[in] );
          
          // (7) Extracting GEMIPM output data to mass-transport program arrays
          node->GEM_to_MT( mt.aNodeHandle[in], mt.aNodeStatusCH[in], mt.aIterDone[in],
              mt.aVs[in], mt.aMs[in], mt.aGs[in], mt.aHs[in], mt.aIC[in], mt.apH[in], mt.ape[in],
              mt.aEh[in], mt.arMB[in], mt.auIC[in], mt.axDC[in], mt.agam[in], mt.axPH[in],
              mt.avPS[in], mt.amPS[in], mt.abPS[in], mt.axPA[in], mt.aaPh[in], mt.abSP[in] );

          // Here the setup of initial differences between node compositions,
          //    temperatures, etc. can be implemented
          //
          // Here the file output for the initial conditions can be implemented
       }  // end loop on in
    }

The last code snippet below shows how the main loop of coupled calculations is organized. 

Firstly, the use of access methods for extracting indexes of independent chemical components and phases is shown.  Then, the stoichiometry of matter to be transported is defined using the stoich[5] work array. Next, the 'time loop' begins with the 'mass transport' as such represented as a single mt.OneTimeStepRun() function call; in principle, in this function, also temperature of each node may change.

After that, the nested 'chemical equilibriation loop' performs re-calculation of equilibria in all nodes whose bulk composition had just been modified at the 'mass transport' stage at this time step. This loop also demonstrates how to use the 'smart initial approximation' (SIA) mode of GEM IPM. After some output indicating changes in chemical speciation at this time step in each node, the  chemical equilibration loop is closed, and control goes to the mass transport time loop. Note that at this stage, various back-coupling calculations, i.e. how precipitation/dissolution of some phases has affected the porosity and transport properties, can be implemented  (in a real coupling, of course).

    // Main loop - iterations over nTimes time steps
    int xCalcite = node->Ph_name_to_xDB("Calcite");
    int xDolomite = node->Ph_name_to_xDB("Dolomite-dis");
    int xAq_gen = node->Ph_name_to_xDB("aq_gen");
    long int ICndx[5];
    ICndx[0] = node->IC_name_to_xDB("Ca");
    ICndx[1] = node->IC_name_to_xDB("C");
    ICndx[2] = node->IC_name_to_xDB("O");
    ICndx[3] = node->IC_name_to_xDB("Mg");
    ICndx[4] = node->IC_name_to_xDB("Cl");
    // Checking indexes
    cout << "xCa= " << ICndx[0] << " xC=" << ICndx[1] << " xO=" << ICndx[2] << " xMg="
         << ICndx[3] << " xCl=" << ICndx[4] << endl << " xCalcite=" << xCalcite
         << " xDolomite=" << xDolomite << " xAq_gen=" << xAq_gen << endl << endl;
    double stoich[5] = { 0., 0., 0., 1., 2. }; // defines what is 'transported'

    long int it;
    for( it=0; it< mt.nTimes; it++ )
    {
       cout << "Time step  " << it << endl;
       // Mass transport loop over nodes (not a real transport model)
       mt.OneTimeStepRun( stoich, ICndx, 5 );

       // Chemical equilibration loop over nodes
       for( in=0; in< mt.nNodes; in++ )
       {
          mt.aNodeHandle[in] = in;
          mt.aNodeStatusCH[in] = NEED_GEM_SIA;
          // (8) Setting input data for GEM IPM to use available node speciation as
          // initial approximation
          node->GEM_from_MT( mt.aNodeHandle[in], mt.aNodeStatusCH[in],
                  mt.aT[in], mt.aP[in], mt.aVs[in], mt.aMs[in],
                  mt.abIC[in], mt.adul[in], mt.adll[in], mt.aaPH[in], mt.axDC[in], mt.agam[in] );
          // (9)   Passing current FMT iteration information into the work DATABR structure
          node->GEM_set_MT( (double)it, 1. );
 
          // Calling GEMIPM calculation
          mt.aNodeStatusCH[in] = node->GEM_run( true );

          if( ( mt.aNodeStatusCH[in] == ERR_GEM_AIA || mt.aNodeStatusCH[in] == ERR_GEM_SIA ||
                        mt.aNodeStatusCH[in] ==  T_ERROR_GEM ) )
          {
              cout << "Error: GEM calculation results are not retrieved. Time step"
                 << it << " node " << in << endl;
          }
          else
          {
            if( ( mt.aNodeStatusCH[in] == BAD_GEM_AIA || mt.aNodeStatusCH[in] == BAD_GEM_SIA  ) )
            {
               cout << "Insufficient quality of GEM solution, but GEM results are retrieved"
               << it << " node " << in << endl;
            }             
            else // (7) Extracting GEMIPM output data to FMT part
              node->GEM_to_MT( mt.aNodeHandle[in], mt.aNodeStatusCH[in], mt.aIterDone[in],
                mt.aVs[in], mt.aMs[in], mt.aGs[in], mt.aHs[in], mt.aIC[in], mt.apH[in], mt.ape[in],
                mt.aEh[in],mt.arMB[in], mt.auIC[in], mt.axDC[in], mt.agam[in], mt.axPH[in],
                mt.avPS[in], mt.amPS[in], mt.abPS[in], mt.axPA[in], mt.aaPh[in], mt.abSP[in] );
          }
          // Here, the output upon completion of the time step is usually implemented
          //  to monitor the coupled simulation or collect results
          cout << "  Node " << in ;
          cout << ": Aq= " << mt.axPH[in][xAq_gen] << " pH= " << mt.apH[in] <<
                  "  Calcite= " << mt.axPH[in][xCalcite] << endl;
      }
   }
   // Calculations finished - end time reached

   // Final output e.g. of total simulation time or of the final distribution of
   //  components and phases in all nodes can be implemented here

   // deleting GEM IPM and data exchange memory structures
   delete node;
   // end of example
   return 0;
}




4. Frequently asked questions

  <under construction>


Last change: 16.10.2012 DK

Copyright (c) 2012 GEMS Development Team