Avanced Options in Tables and Universal Scripting
This is a Fortran wrapper for the Lua scripting language, dedicated to enable flexible configuration of Fortran applications with this full fledged scripting language. Aotus is also the Night monkey (living in south america). Thus, it can be understood to be interacting with the moon (Lua, provided by Pontifical Catholic University of Rio de Janeiro in Brazil) and providing the means to retrieve data from its scripts.
The most prominent data structure in Lua are tables, which provide the possibility to store complex data structures. Thus, the configuration is usually done by global variables in the Lua script or tables.
Aotus provides several layers, encapsulating the bare Lua C-API:
On top of those there is an additional aot_vector_module that allows the direct reading of values into arrays of rank 1.
Finally, there is an additional module which allows output of Fortran values into nested Lua tables.
The library can be compiled by various modern Fortran compilers as outlined in Compiler Support.
An example, showing the usage of this library in a Fortran application, is given in aotus_sample.f90. You can find it in the build directory. The corresponding Lua script is given in config.lua, which you can find in the sample directory
Note on usage in parallel environments: Aotus itself is not providing parallel facilities. But it can be nicely used in parallel aswell. However, for massively parallel systems, it is advisable to minimize the access to config files. To avoid excessive filesystem meta accesses it is recommended to load required files only on one process. An implementation of this for MPI can be found in TreElMs distconf.
You need a handle for the Lua context of type flu_State. You can get that by opening and processing a Lua script with open_config_file:
call open_config_file(L, filename, errCode, errString)
Instead of reading a script from a file, you can also execute a string directly by using open_config_chunk with the same interface, but replacing the filename with the chunk of Lua code to be executed. The arguments errCode and errString are optional and return errors, which might occur while loading or executing the Lua code.
It is also possible to load already processed scripts in byte code by using the open_config_buffer routine, which expects an array of characters with the script in byte code.
In the end, after getting all configuration values, close it again with close_config:
call close_config(L)
From configuration files you usually want to obtain some parameters to steer your application. The most important interface for this functionality is aot_get_val, it is a generic interface to different functions, which allow you to obtain global values, values within tables and complete vectors as tables. Sometimes, especially in the context of evaluating functions, you might also need aot_top_get_val, which always tries to obtain the topmost value on the Lua stack.
The aot_get_val interface is provided by the aotus_module and generically looks like this:
call aot_get_val(val, errCode, L, key, default)
Where:
In general we get the following shape for the interface:
call aot_{top}_get_val(<outputs>, <id>, default)
Where outputs is: val and errCode and id is at least the Lua context (L) for the aot_top variant. For global variables there has to be a key in the id and for tables there has to be a thandle. In tables the key might be replaced by a pos argument.
The interface to work with tables is trying to resemble IO, thus you could think of a table as a file, which you can open and read values out of by referencing its unit (handle). Opening and closing tables is provided by the aot_table_module.
To work with a table, you first need to get a handle to identify the table. For globally defined tables this can be done by using
call aot_table_open(L, thandle, key)
Where:
For a table within an already opened table use:
call aot_table_open(L, parent, thandle, key, pos)
Where the additional arguments are:
The handle will be 0, if the variable does not exist, or is not a table. After you have the handle to the table, you can access its components with
call aot_table_get_val(val, errCode, L, thandle, key, pos, default)
Which is essentially the same interface as for global variables, except for the optional argument pos, by which the unnamed entries in the table are accessible by their position and the handle to the table, where the component is to be looked up. Both pos and key are optional, providing the ability to access the variables either purely by their order, or their name. If both are provided, the key takes precedence. The handling of positional or named addressing is a little bit similar to the Fortran convention, that is, as soon as there is a named component in the table, all following components should also be named. Positional references are only valid up to this position.
After all values are read from the table, the table should be closed again by calling
call aot_table_close(L, thandle)
Sometimes there might be different types possible for a given setting, and different actions need to be taken for each possible type. In this case you might first want to check the type of a given variable before proceeding and reading the actual value. This can be achieve by aot_type_of, which is a function that will put the requested variable onto the top of the stack and return the Lua data type of it:
luatype = aot_type_of(L, thandle, key, pos)
With:
Afterwards, the actual value can then be read by the corrsponding aot_top_get_val.
Again functions try to resemble the usage of files, however in this case its slightly more complicated, as you first need to "write" the input parameters into the function, then execute it and finally retrieve the results.
To use a function, that is globally defined, open it with:
call aot_fun_open(L, fun, key)
Where:
To access a function, which is within a table, use:
call aot_fun_open(L, parent, fun, key, pos)
Where the additional arguments are:
After the function is opened, its arguments need to be filled with:
call aot_fun_put(L, fun, arg)
Where:
When all arguments are written, the function needs to be executed with:
call aot_fun_do(L, fun, nresults)
Where:
After the function is executed, the results can be read, using:
call aot_top_get_val(val, errCode, L, default)
Where:
You will get the results in reversed order if there are multiple results. That is, because the first call to aot_top_get_val will return the last result returned by the function, the next the second last, and so on.
You may then go on and put new arguments into the function, execute it and retrieve the corresponding results.
After you are done with the evaluation of the function it has to be closed with:
call aot_fun_close(L, fun)
This should cover the most typical tasks for configurations.
In addition to the scalar retrieval routines, there is an aot_vector_module provided, which provides interfaces with arrays of rank 1 to access vectorial data in form of tables. They follow the same interfaces, as the scalar routines, however the values, error codes and defaults have to be one-dimensional arrays.
call aot_get_val(val, errCode, L, key, default)
Where:
The interface for vectors within other tables is defined accordingly. In the interface described above, the conf_val vector has a given size and just the values need to be filled. However, it might be necessary to retrieve arrays, of which the size is not known beforehand, and should depend on the table definition in the configuration. For these cases there are additional routines defined, which take allocatable arrays as input. These are then allocated and filled according to the configuration or the provided default vector. In these interfaces you have to provide an additional parameter maxlength that limits the size of the vector to allocate. If the Lua variable in question is not a table but rather a scalar of the correct type, an array of length 1 will be created for this single value. For undefined vectors zero sized arrays are returned for the values and the error codes.
Aotus also provides the interface to register functions in Lua to allow the usage of your Fortran implementations in Lua. A short description for this feature is provided in Using Fortran in Lua.