3.2. Data types¶
As stated before, we use C data types for intermediate representation, as C is the lingua franca of programming languages, and they all have facilities to communicate with C and making function calls to C. Also, popular languages such as Python and Julia have a C API that provides means of conversion of the data from the intermediate representation to the native data types of these languages.
Particularly, it is easy to convert Python and Julia integer data types to
C int
data type, provided that the integers are representable
in 32 bits.
Also, conversion of binary double-precision floating-point numbers
is straightforward between C and other languages due to the widespread use
of the IEEE 754 standard
for floating-point arithmetic.
Data marshalling of arrays of double-precision floating-point numbers
is made possible by using an auxiliary data structure OIFArrayF64
that,
similarly to NumPy arrays or Julia arrays,
represents \(n\)-dimensional array for given
\(n \in \mathbb N\) and packs data together with the number of dimensions \(n\)
and the array shape, that is, the size of the array along each dimension.
This data structure enables a~uniform function signature
among supported languages (currently C, Python, and Julia)
as then the arrays are given as a single
function argument in all these languages (in contrast with traditional use
of C arrays, where data and dimensions are provided as separate arguments).
Correspondingly, we use NumPy C API and Julia C API to convert to
OIFArrayF64
and back when needed.
We also support read-only strings that can be used to pass information such as, e.g., a name of an integrator.
As it is common in scientific computing to pass callback functions to numerical
solvers, Open Interfaces support passing functions between different languages.
This is achieved in the following manner.
Additional data structure OIFCallback
is used that encodes information
about the original language of the callback function, the function itself
in this language, and the C-compatible version of this function.
Consequently, on the language-specific dispatch level, if the user-facing
and implementation languages are the same, the original callback function
is used to avoid performance penalties, while the C-compatible callback
is wrapped in the programming language of the implementation.
Additionally to the callback, passing a generic memory pointer is supported which is required, for example, to pass context to the callback functions. Although in languages like Python one can simply use closures to pass the context, in languages like C it is the only way to achieve this.
Finally, simple dictionaries of key-value pairs, where keys are strings, and values are either integer or floats, are supported to pass generic options that are implementation-specific.
For each supported data type, the data are passed between software components along with integer identifiers allowing to restore the type on the receiver end. We use the following symbolic constants further in the text to refer to the actual data types:
OIF_INT
: 32-bit integers,OIF_FLOAT64
: 64-bit binary floating-point numbers,OIF_ARRAY_F64
: arrays of 64-bit binary floating-point numbers,OIF_STR
: strings with one-byte charactersOIF_CALLBACK
: callback functions,OIF_USER_DATA
: user-data objects of volatile type,OIF_CONFIG_DICT
: dictionary of key-value options pairs.
It is assumed that each symbolic constant is replaced with the actual data type
when used in a particular language: for example, OIF_ARRAY_F64
resolves to the provided data structure OIFArrayF64
in C
and to NumPy arrays with dtype=numpy.float64
in Python.