API Changes in Python --------------------- The ``basic`` module is intended to be a copy of the original ChemApp :term:`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: .. code-block:: python 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: .. code-block:: python 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: .. code-block:: c 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 :term:`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: .. code-block:: python from chemapp import basic as chemapp chemapp.tqini() nphase = chemapp.tqnop() Executing this code results in the following output from Python: .. code-block:: none Traceback (most recent call last): File "", line 1, in 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: .. code-block:: python 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: .. code-block:: none 104 You can, of course, also use Python's exception handling infrastructure to catch and handle the error yourself. .. code-block:: python 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 :term:`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. .. code-block:: c 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: .. code-block:: python 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. .. code-block:: python 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: .. code-block:: 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: .. code-block:: python 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 :term:`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. .. code-block:: c 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. .. code-block:: python 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 :term:`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. .. code-block:: fortran ! 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. .. code-block:: python # 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. .. code-block:: python # 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.