Skip to content

Enable development of models written in high-level languages (e.g. Python) #79

@speth

Description

@speth

Abstract

The ultimate goal of this project is to allow users to write their own models for thermodynamics, kinetics, reactors, etc. in easy-to-use high level languages like Python, rather than requiring models to be implemented in C++.

For the sake of simplicity, this proposal is written with reference to models created in Python, although it could apply to other languages like Julia or anything else that exposes a C API to be able to call code written in that language.

Motivation

Currently, all of the models present in Cantera, representing everything from species thermodynamic properties to reaction rates to reactor governing equations, are implemented in C++. While this is computationally efficient, creating new models in C++ is a significant barrier to scientific users who are not necessarily expert software engineers, as well as being time-consuming even for those who are. This barrier discourages exploratory model implementations, which are valuable in many cases where those new models need to be evaluated and refined within the context of other Cantera capabilities, e.g., a new thermodynamic model that must be evaluated within the context of kinetic and transport calculations. Allowing new models to be implemented in high level languages like Python will reduce this barrier, providing scientific users with an environment that is well-suited for rapid prototyping and making it easy to share their developments as standalone modules. For a model that proves to be useful and needs to be used in performance-sensitive applications, a Python implementation could later be used as a “reference implementation” for the C++ version of the model.

Description

Cantera's interfaces to other languages mostly involve code in other languages calling the Cantera library through its C or C++ APIs. Implementing the capability described here requires the reverse - having Cantera call code written in another language which presents a C or C++ API. The Cantera Python interface already does this in one particular instance, for functions that provide reactor inputs as a function of time, where users can provide this information as native Python functions. While the implementation under the hood is not trivial (see func1.pyx and funcWrapper.h), in the case of Python, many of the difficulties such as translating C++ exceptions into Python has already been worked out, and the additions required are mostly just to handle functions with different forms besides double = f(double).

The next step is then to allow the user to define a Python class that overrides some virtual member functions of an existing Cantera C++ class, such as ThermoPhase. For this feature to be most useful, users should be able to start with any of the existing Cantera phase models. The implementation for this that I think would work is introduce a new templated class which derives from the base ThermoPhase type specified by the user, which holds a set of objects encapsulating the user-defined functions. A skeleton of this class might look something like the following:

template <class T>
class UserDefinedPhase : public T {
public:
    // An override of a ThermoPhase function
    virtual void getPartialMolarEnthalpies(double* hbar) const {
        if (m_getPartialMolarEnthalpiesFunc) {
            m_getPartialMolarEnthalpiesFunc(hbar);
        } else {
            T::getPartialMolarEnthalpies(hbar);
        }
    }

    // Setup function; this would need overrides for each distinct function signature handled
    void setOverride(const std::string& name, std::function<void(double*) func) {
        if (name == "getPartialMolarEnthalpies") {
            m_partialMolarEnthalpiesFunc = func;
        }
    }

private:
    std::function<void(double*)> m_getPartialMolarEnthalpiesFunc;
}

template class UserDefinedPhase<IdealGasPhase>;

This is of course a fairly "simple" example, and there are certainly some complexities to work out, like how to allow the user defined functions invoke the method of the base class.

On the Python side, I think the user would create a class that inherited from a Python class UserDefinedPhase. The UserDefinedPhase.__init__ method would then be responsible for encapsulating the methods in the user's class and calling the C++ setOverride method.

Some additional machinery that I haven't thought too deeply about yet would be required to allow Cantera to load the user's Python module based on some information defined in the input YAML file.

I think the approach outlined here would work well for the ThermoPhase, SpeciesThermoInterpType, Kinetics, Reactor, and Domain1D classes. One important exception to this at the moment is Reaction classes (or really, the reaction rate classes) which don't follow the same pattern of using (essentially) abstract base classes in a generic manner, which makes it difficult to add new reaction types even from C++. I think an overhaul there (per #63) is in order before this approach could be applied to reactions.

Alternatives

I don't know of an alternative that would provide the same flexibility. Perhaps something based on code generation would be an option, although I think that would require unique solutions for each supported language, and I don't think it would be able to provide the same coupling with Cantera's existing models. I suppose "rewrite Cantera from scratch in your own favorite language", but that's quite the time investment, and in the case of Python would come at a huge performance penalty as well.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    work-in-progressAn enhancement that someone is currently working on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions