API Changes in Python

The basic module is intended to be a copy of the original ChemApp API, which is available in languages such as Fortran and C, to Python. Python, however, is quite different in nature to these languages, and using its language features holds many benefits. It is able to help you write less code, make fewer mistakes, and get more done quickly. For these reasons we changed the original ChemApp API to be a bit more Pythonic.

Indexing: 0-based instead of 1-based

ChemApp uses names and indexes to identify system components, phases, phase constituents, sub-lattices, sub-lattice constituents, etc. The names are simply text strings like gas_ideal and SiO2. The indexes are integer numbers.

ChemApp uses the number 1 as index for the first element in a list. We call this one-based or 1-based indexing. Most modern programming environments like C, C++, Java, Go and Python use zero as index of the first element, which is referred to as (you guessed it) zero-based or 0-based indexing.

We decided to use zero-based indexing in ChemApp for Python, since that is what Python uses. This allows us to write less code, and make fewer mistakes. If we remained with ChemApp’s 1-based indexing, you would have to write a loop as follows:

for phase_index in range(1, chemapp.tqnop()+1):
  print(chemapp.tqgnp(phase_index))

The range(1, chemapp.tqnop()+1) bit gives us two opportunities to make a mistake. Now that we have chosen 0-based indexing, the loop can look like this:

for phase_index in range(chemapp.tqnop()):
  print(chemapp.tqgnp(phase_index))

You no longer needed to manage the gap between the Python world and the ChemApp world. ChemApp for Python does that internally.

Error Handling

The following code snippet shows how to process error information with the original ChemApp API in the C programming language:

int main()
{
  LI noerr, nphase;
  int i;

  // initialise chemapp
  tqini(&noerr);

  // invoke an error by trying to get the number of phases before
  // reading a thermochemical data-file
  tqnop(&nphase, &noerr);

  // check for error, and process it
  if (noerr) {
    printf(" ChemApp reports error no. %li\n", noerr);

    // retrieve the error message
    tqerr(TQERRMSG,&noerr);

    // display error message
    for(i=0;i<3;i++)
      printf("%s\n",TQERRMSG[i]);
  }

  return 0;
}

All the tq* functions in the original ChemApp API take an integer called noerr as the last parameter. ChemApp returns a non-zero value into this variable to indicate that an error occurred. This means that every call to ChemApp must be followed by an if statement to check the value of noerr. This is cumbersome, and results in many extra lines of code.

In ChemApp for Python, ChemApp’s error-handling mechanism is hidden. If an error occurs, a chemapp.core.ChemAppError exception is thrown. This means you will always be notified when an error occurs, without having to write extra code. This is what the preceeding block of C code looks like in Python:

from chemapp import basic as chemapp

chemapp.tqini()
nphase = chemapp.tqnop()

Executing this code results in the following output from Python:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "chemapp/basic.pyx", line 906, in chemapp.basic.tqnop
chemapp.core.ChemAppError: A thermodynamic data-file must be read first.

If you want to acquire the ChemApp error code (this can be very informative), it is possible to obtain it like this:

from chemapp import basic as chemapp
from chemapp.core import ChemAppError

chemapp.tqini()
try:
   ca.tqnop()
except ChemAppError as ca_err:
   print(ca_err.errno)

which will result in the following output:

104

You can, of course, also use Python’s exception handling infrastructure to catch and handle the error yourself.

from chemapp import basic as chemapp
from chemapp.core import ChemAppError

try:
    chemapp.tqini()
    nphase = chemapp.tqnop()
except ChemAppError:
    print('A ChemApp error occurred!')

Function Return Values

The original ChemApp API passes results back to the user through variables provided as parameters to the tq* function calls. Here is a C example for the tqnosc function that determines the number of system components in the loaded data file. The variable nscom is used to store the result.

int main()
{
    LI noerr, nscom, i;
    char fname[13], scname[TQSTRLEN];

    tqini(&noerr);  // initialise chemapp.

    strcpy(fname,"cosi.dat");  // specify the file name.
    tqopna(fname,10,&noerr);  // open "cosi.dat" for reading.
    tqrfil(&noerr);  // read the file.
    tqclos(10,&noerr);  // close the file.

    // get the number of system components.
    tqnosc(&nscom, &noerr);

    printf("Number of system component in file %s: %li\n", fname, nscom);

    return 0;
}

ChemApp for Python passes results back by function return values, and not function parameters, which is more intuitive. Here is the equivalent Python code:

from chemapp import basic as chemapp

chemapp.tqini()  # initialise chemapp.

fname = "cosi.dat"  # specify the file name.
chemapp.tqopna(fname, 10)  # open "cosi.dat" for reading.
chemapp.tqrfil()  # read the file.
chemapp.tqclos(10)  # close the file.

# get the number of system components.
nscom = chemapp.tqnosc()

print(f'Number of system components in file {fname}: {nscom}')

For functions that return more than one result, such as tqstpc, which obtains the stoichiometry and molar mass of a phase constituent, ChemApp for Python returns the results as a tuple that can easily be assigned to multiple variables when calling the function. The following example demonstrates this.

from chemapp import basic as chemapp

chemapp.tqini()  # initialise chemapp.

fname = "cosi.dat"  # specify the file name.
chemapp.tqopna(fname, 10)  # open "cosi.dat" for reading.
chemapp.tqrfil()  # read the file.
chemapp.tqclos(10)  # close the file.

# get stoichiometry and molar mass of constituent '2' in phase '1'.
stoich, mm = chemapp.tqstpc(1, 2)

Function Parameter Default Values

Some of the ChemApp tq* functions are very often used with the same set of parameters. It therefore makes sense to assign default values to these parameters to reduce the amount of code that you need to write. C and Fortran do not make provision for this, but Python does.

Some functions in ChemApp for Python basic therefore have default values for their arguments. The default values were chosen as the values that you are most likely to use. Now you can omit the parameter values altogether if they don’t differ from the defaults. This results in less code, and less clutter.

Let’s have a look at the tqconf function as an example. tqconf currently supports only one option E, which is used to for semi-automatic charge balance correction. This is usually done for the entire thermochemical system, but you can also specify the electron system component for a single phase. To do the correction for the entire system, you do the following in C:

tqconf("E", 0, 0, 0, &noerr);

This means that three of the function parameters, valuea, valueb, and valuec are not really used, but you still have to type them. ChemApp for Python assigns a default value of -1 to each parameter. (Remember that ChemApp for Python is zero-based, and ChemApp is one-based.) The ChemApp for Python equivalent of the function call is therefore:

chemapp.tqconf(ConfigurationOption.E)

Other functions that have default parameters values include tqce, tqcel, tqcen, tqcenl, tqgetr, and tqgdat.

Enumerations Instead of Strings

In the original ChemApp API, hard-coded strings are passed as option parameters to many tq* functions. These strings are listed in the user documentation and entered manually into the code. An example of this is seen in the tqgio function that takes an input-output option as first parameter. The possible values are FILE, ERROR, LIST, and LANGUAGE. In the C code snippet below, the option, FILE, is hard-coded into the tqgio function call.

int main()
{
  LI noerr, iovalue;

  tqini(&noerr);
  tqgio("FILE", &iovalue, &noerr);

  return 0;

}

Hardcoding such values can lead to errors, and these may be difficult to identify and fix later. Hard-coding is seen as bad coding practice and should be avoided if possible.

ChemApp for Python uses enumerations to overcome this problem. All the option lists used in ChemApp were turned into Python enumerations. You do not need to look up the option values any more if you are working with an IDE like PyCharm, for example. It will indicate the available options as you type code. Below is the Python equivalent to the C code above.

from chemapp.core import IoOption
from chemapp import basic as chemapp

chemapp.tqini()
iovalue = chemapp.tqgio(IoOption.FILE)

The enumerations used in ChemApp for Python basic, together with their associated functions are presented in the core module chapter.

Values Returned by tqsetc

In the original ChemApp API a call to tqsetc returns a number to identify the equilibrium condition that is set. For system-level conditions it is always the same value, but conditions related to incoming amounts, chemical potentials and relative activities have unique numbers.

In most cases the returned condition number is not needed in the calculation being done, and can therefore be ignored. In ChemApp for Python, you can simply not assigned returned value to a variable. Here is a Fortran example.

! declare variables
     INTEGER NUMCOM, ISN, IPB

! get system component indexes
     CALL TQINSC('Pb ', IPB, NOERR)
     CALL TQINSC('Sn ', ISN, NOERR)

! set the incoming amounts for the Sn and Pb
     CALL TQSETC('IA ', 0, ISN, 0.2D0, NUMCON, NOERR)
     CALL TQSETC('IA ', 0, IPB, 0.8D0, NUMCON, NOERR)

Here is a Python example in which we do want to use the returned condition numbers.

# get system component indexes
ipb = chemapp.tqinsc('Pb')
isn = chemapp.tqinsc('Sn')

# set the incoming amounts for the Sn and Pb
condition_sn = chemapp.tqsetc(ConditionVariable.IA, -1, isn, 0.2)
condition_pb = chemapp.tqsetc(ConditionVariable.IA, -1, ipb, 0.8)

Finally, in the next example we simply ignore the condition number.

# get system component indexes
ipb = chemapp.tqinsc('Pb')
isn = chemapp.tqinsc('Sn')

# set the incoming amounts for the Sn and Pb
chemapp.tqsetc(ConditionVariable.IA, -1, isn, 0.2)
chemapp.tqsetc(ConditionVariable.IA, -1, ipb, 0.8)

This is another example of how ChemApp for Python and Python help you to get more done with less fuss.