-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Abstract
The aim of this enhancement is to introduce a mechanism for generating the code needed to implement each of Cantera's language interfaces based on a language-agnostic description. This would make it easier to maintain the existing language interfaces, help keep their interfaces consistent and complete, and simplify the introduction of interfaces to additional languages.
Motivation
Currently, Cantera's interfaces to languages other than C++ are all implemented semi-independently. Any new feature to the C++ core which meant to be part of the public interface needs to have wrappers manually added to the Python, C, Matlab, and Fortran interfaces. In many cases, these wrappers are either not added, or are added only to a subset of the interfaces, with the Matlab and Fortran interfaces being the least complete. Adding a new language interface (e.g. for Julia) would require writing a huge number of wrapper functions. Large portions of this code follow very simple patterns, which mostly amount to translating how different languages handle arguments and data types like strings or arrays, as well as different naming conventions, which suggests that some of this repetitive code could be generated automatically.
Possible Solutions
Introduce a YAML file providing descriptions of each class and method that would be implemented as part of a Cantera interface. This file would provide information about the input and output types for the function, documentation for the function, etc, that could be used to construct the necessary wrapper code. For example:
class:
name: ThermoPhase
prefix: thermo
cabinet: ThermoCabinet
methods:
- name: nSpecies
arguments: []
returns: size_t
- name: setMoleFractions
arguments:
- {type: double, dimensions: [nSpecies]}
returns: voidThis could then be used to fill in templates, using a standard Python templating library like Jinja. The easiest way to write the wrappers would probably be to do one for each distinct function signature, given how many functions in Cantera have the same signature, e.g. scalar getter/setter, array getter/setter, etc. For the above functions, the templates for the C wrappers might look like:
scalar getter:
// size_t thermo_nSpecies(int n) {
{{ method.returns }} {{ class.prefix }}_{{ method.name }} (int n) {
try {
// return ThermoCabinet::item(n).nSpecies();
return {{ class.cabinet }}::item(n).{{ method.name }}();
} catch (...) {
return handleAllExceptions(npos, npos);
}
}array_setter:
// int thermo_setMoleFractions(int n, double* arg0, size_t len_arg0) {
int {{ class.prefix }}_{{ method.name }} (
int n,
{{ method.arguments[0].type }}* arg0,
size_t len_arg0) {
try {
// auto& p = ThermoCabinet::item(n);
auto& p = {{ class.cabinet. }}::item(n);
// checkArraySize(len_arg0, p.nSpecies());
checkArraySize(len_arg0, p.{{ arguments[0].dimensions[0]}}());
// p.setMoleFractions(arg0);
p.{{ method.name }}(arg0);
return 0;
} catch (...) {
return handleAllExceptions(-1, ERR);
}
}This pseudo-code undoubtedly contains some errors, and I know there are many special cases that I haven't thought of yet in terms of what will need to be encoded in the YAML file, but I think this approach can be generalized to make maintaining a multitude of language interfaces for Cantera much easier.
One benefit of using this approach for the Matlab toolbox in particular is that it would eliminate most of the pain associated with the magic numbers which are used to specify the correct method to call within the single entry point of the ctmethods mex file, since they could be determined automatically.
I think this approach would also be useful for implementing at least some portions of the Python interface.
A few issues in the descriptions that I know need some thought:
- naming conventions between different languages
- handling of values that are logically return values, but appear as arguments in C++, i.e.
getMoleFractions. - Transitioning from the existing interfaces to interfaces generated this way, given that there will probably be a few API changes
Alternatives:
SWIG is general tool for generating wrapper interfaces for a number of languages. I considered it when overhauling the Python interface, and decided not to use it in part because it wasn't able to produce a particularly idiomatic interface, i.e. using things like "properties" in Python, or adopting naming conventions appropriate for the target language. It also does not have an interface for producing Matlab wrappers. Edit: SWIG also does not currently support Julia.