Skip to content

Use code generation to provide interfaces for C, Fortran, Matlab, Julia, etc. #39

@speth

Description

@speth

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: void

This 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions