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.