0% found this document useful (0 votes)
25 views31 pages

A General Approach For Running Python Co

general_approach_for_running_Python_co

Uploaded by

Enrique Ramos
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views31 pages

A General Approach For Running Python Co

general_approach_for_running_Python_co

Uploaded by

Enrique Ramos
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 31

A GENERAL APPROACH FOR RUNNING PYTHON CODES IN OPENFOAM USING

AN EMBEDDED PYBIND11 PYTHON INTERPRETER

SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

School of Mechanical and Materials Engineering, University College Dublin, Ireland1 .


arXiv:2203.16394v1 [cs.CE] 30 Mar 2022

Email address: [email protected]

Abstract. As the overlap between traditional computational mechanics and machine learning grows, there is

an increasing demand for straight-forward approaches to interface Python-based procedures with C++-based

OpenFOAM. This article introduces one such general methodology, allowing the execution of Python code

directly within an OpenFOAM solver without the need for Python code translation. The proposed approach

is based on the lightweight library pybind11, where OpenFOAM data is transferred to an embedded Python

interpreter for manipulation, and results are returned as needed. Following a review of related approaches,

the article describes the approach, with a particular focus on data transfer between Python and OpenFOAM,

executing Python scripts and functions, and practical details about the implementation in OpenFOAM. Three

complementary test cases are presented to highlight the functionality and demonstrate the effect of different

data transfer approaches: a Python-based velocity profile boundary condition; a Python-based solver for

prototyping; and a machine learning mechanical constitutive law class for solids4foam which performs field

calculations.

1. Introduction

Appearing in 1985, C++ was motivated by the limitations of the structured programming paradigm:

code reusability, code sharing, and code extensibility [1]. Built on code abstraction and pioneering the

use of object-oriented programming (OOP), C++ quickly became the number one choice for the creation

of large, complex, and extendable software. In fact, before C++, OOP was mostly unknown in industry

as its techniques were believed to be excessively expensive for real-world applications and too complex for

non-expert programmers [2]. OpenFOAM was one of the first large computational mechanics softwares to

embrace C++ and OOP, tracing its origin to its predecessor ‘FOAM’ with its roots in the late 80’s. Using

OOP techniques, the authors of FOAM represented computational entities using mathematical language,

without sacrificing computational efficiency [3].

Over the subsequent three decades, there has been an explosion in the availability of computational re-

sources, in particular via parallel computing, as well as C++ software to exploit it. However, as acknowledged
1
2 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

by Bjarne Stroustrup when creating C++, code elegance was not its primary goal: “Even I knew how to

design a prettier language than C++” [2]. The aim for C++ was utility, not abstract beauty. In contrast,

Python, publicly released in 1991, aimed to achieve a higher level of abstraction which emphasised readability

and usability, while achieving it with simpler syntax and fewer rules. As a result, when compared to lower-

level languages such as C++, Python’s learning curve is less steep and requires considerably less effort for

prototyping code and performing auxiliary tasks such as handling files; however, these benefits are typically

achieved at a higher computational cost [4].

Over time, Python has become one of the most widely used programming languages, with the ‘Popularity

of Programming Languages Index’ [5] placing it as the most popular programming language in the world.

This momentum has resonated with the OpenFOAM community, which has tried to merge the advantages

of Python with OpenFOAM, as demonstrated by projects such as:

• PyFoam: A widely used library for manipulating OpenFOAM cases and controlling OpenFOAM runs

[6];

• PythonFlu: a Python-based OpenFOAM wrapper which aims to make the calculation environment

more interactive and automated [7]; however, in recent years this project has become inactive;

• fluidfoam: Python classes for plotting OpenFOAM data [8];

• Owls: Python tools for data analysis and plotting of OpenFOAM cases [9].

In general, these projects have used Python to improve the user experience or develop pre/post processing

OpenFOAM utilities. On the other hand, swak4Foam [10], which is a collection of parsers for expressions

on OpenFOAM-types, was designed with the goal of minimising the use of C++ in OpenFOAM. With this

premise, one of its functionalities offers a function object that can execute Python code using an integrated

Python interpreter. In addition, Maulik et al. [11] and Weiner et al. [12, 13] have aimed to provide capabilities

to use data analysis tools from Python within OpenFOAM; the former constructed OpenFOAM applications

that have bindings to data libraries in Python and the latter transfers data and neural network models from

Python to OpenFOAM via the PyTorch library. More recently, Anderluh and Jasak [14] have proposed to

re-write the core OpenFOAM functionality entirely in Python.

Inspired by swak4Foam, the current article proposes a general approach for integrating an embedded

Python interpreter in OpenFOAM via the pybind11 library [15], with a particular emphasis on its use with

machine learning techniques. In particular, the approach is motivated by two opportunities:

(1) The ability to rapidly prototype numerical algorithms using Python, while taking advantage of the

OpenFOAM framework;
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 3

(2) The capacity to exploit the vast ecosystem of scientific programming tools that have been developed

for Python, particularly those in the area of machine learning and data science.

Related to the second point, the recent machine learning revolution has lead to the nascent area of hy-

brid machine-learning-computational-mechanics procedures, including numerous research avenues: machine

learning-acceleration of solvers [16, 17]; machine learning-constitutive laws in solid mechanics [18, 19, 20];

machine learning-turbulence models in fluid dynamics [21]; and targeting both acceleration and accuracy

gains [22]. In these approaches, the first challenge involves the development of a machine learning procedure

- typically based on a neural network - capable of solving the required task; this is commonly done with

Python, or similar languages such as R, Matlab or Julia. The next immediate challenge consists of cou-

pling the machine learning model with a computational mechanics solver, whether OpenFOAM or another;

currently, there is no standard way of doing this in OpenFOAM. Possible approaches include:

(1) Using the TensorFlow C++ API where OpenFOAM is linked at compile time to TensorFlow [23];

(2) Using the Python C API combined with the NumPy package C API [11];

(3) Translating the Python-based neural network to C++ code via specialised conversion tools, such as

frugally-deep [24].

These solutions all require the Python code and/or models to be translated or converted to allow their

use with C++ in OpenFOAM. The primary conceptual disadvantage with this is that it conflicts with the

main motivation of many programmers who want to use Python: coding simplicity; for example, this is

particularly evident when attempting to compile and link the TensorFlow C++ API with OpenFOAM,

where both must be built from source with consistent settings. These challenges can limit the adoption of

Python as a development tool for applications beyond traditional machine learning for which Python might be

well-suited; individual components of OpenFOAM solvers that are particularly suitable are field calculations,

boundary conditions, function objects, and pre/post processing utilities.

Motivated by this, the current article presents a general approach to execute Python code from within

OpenFOAM without the need to convert the Python code to C++. This approach uses an embedded Python

interpreter based on the lightweight library pybind11 [15]. The approach, which is similar to the approaches

adopted in swak4Foam [10] and by Maulik et al. [11], enables the use of Python classes/functions/libraries

via their “pure” Python interfaces, not lower level ones.

The remainder of this article is structured as follows: Section 2 presents an introduction to the required

pybind11 support including a general description on pybind11 and how to include it in OpenFOAM; this is

followed by a description of the interaction between OpenFOAM and Python. In Section 3, three different test

cases are used to illustrate how the proposed approach can be used in OpenFOAM. The first case demonstrates
4 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

how to implement a general velocity profile boundary condition via Python. The second case shows how the

Python interpreter can be used to quickly test “proof of concept” Python numerical implementations via

an OpenFOAM solver. The final case gives an example of how to perform field calculations via analytical

calculations in Python as well as via TensorFlow/Keras machine learning models in Python; in this case,

an elastic mechanical constitutive law for a solid mechanics simulation is chosen for demonstration, but the

approach is equally applicable to any other field calculation. Section 4 summarises the main findings and

learning points of the article, and briefly discusses future directions.

2. Approach and implementation

This section gives a brief description of pybind11, followed by steps on how to use it with OpenFOAM.

After explaining the high level interaction between OpenFOAM and Python via pybind11, the core technical

steps are outlined:

• Initialising the Python interpreter;

• Transferring data between OpenFOAM and the Python interpreter;

• Executing Python code from OpenFOAM.

2.1. The pybind11 library. pybind11 is a lightweight header-only library that is primarily used to allow

C++ code to be called from Python [15]; however, as used in this article, pybind11 also offers the possibility

of embedding a Python interpreter in a C++ application.

Installing pybind11 with Conda Conda is an open source package and environment management

system, which can be used for managing Python environments. Using Conda, pybind11 can be installed in

a *nix terminal with:



1 $> conda install −c conda−forge pybind11
✝ ✆

Including pybind11 in OpenFOAM Once pybind11 is installed on the system, it can be used in an

OpenFOAM application or library by appropriate modification of the OpenFOAM Make/options file:

• Declare two variables, PYTHON INC and PYTHON LIB DIR, at the top of the options file to store the

location of the required pybind11 header files and the Python libraries:

1 PYTHON INC DIR := $(shell python3 −m pybind11 −−includes)

2 PYTHON LIB DIR := $(shell python3 −c "from distutils import sysconfig ; \

3 import os.path as op; v = sysconfig . get_config_vars (); \

4 fpaths = [op.join(v[pv], v[‘ LDLIBRARY ’]) for pv in (‘ LIBDIR ’, ‘LIBPL ’)]; \

5 print(list( filter (op.exists , fpaths ))[0]) " | xargs dirname)


✝ ✆
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 5

where the shell commands here use the python3 command to lookup the location of the include

and library directories.

• Include PYTHON INC DIR in the EXE INC field, for example:

1 EXE INC = \

2 $(PYTHON INC DIR)


✝ ✆

• Include PYTHON LIB DIR and the Python C dynamic library in the EXE LIBS field (or in the LIB LIBS

field if compiling a library), for example:



1 EXE LIBS = \

2 −L$(PYTHON LIB DIR) \

3 −lpython3.8
✝ ✆

where in this case, Python version 3.8 is used, but the programmer should change this to whichever version

they are using.

Including the required header files The header files that will be required in the OpenFOAM/Python

communication depend on the specific interaction scheme used by the programmer; however, in general the

following header files should be included in the OpenFOAM source code:



1 #include <pybind11/embed.h>

2 #include <pybind11/eval.h>

3 #include <pybind11/stl.h>

4 #include <pybind11/numpy.h>
✝ ✆

These four header files provide the following functionality:

• embed.h: Enables the functionality required to embed the Python interpreter in a C++ program;

• eval.h: Allows Python code to be executed/evaluated from C++ in the embedded Python inter-

preter;

• stl.h: Tools for converting data from C++ standard template library containers, such as std::vector,

to Python arrays and vice versa;

• numpy.h: NumPy is the workhorse of scientific computation in Python, and although it is not strictly

required, it is often convenient and efficient to perform Python computations via NumPy. This

header allows to use the py::array data type, which is a C++ wrapper for NumPy arrays. When

a py::array is sent to the Python interpreter, the wrapper is removed and the data is exposed as a

NumPy array.
6 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

Assuming pybind11 was installed correctly, these header files should all be available within the PYTHON-

INC DIR directly defined in the Make/options file.

Note on setRootCase.H OpenFOAM applications typically include a header file named setRootCase.H

which is used to read and check command-line arguments; however, the default arguments which are used

when constructing the Foam::argList object within setRootCase.H can cause issues with certain Python

modules, such as TensorFlow, as noted in online forums [25]. This can be solved by modifying the default

setRootCase.H file in the following way:



1 // Foam::argList args(argc, argv); // previous line

2 Foam::argList args(argc, argv, true, true, false);

3 if (!args.checkRootCase())

4 {

5 Foam::FatalError.exit();

6 }
✝ ✆

2.2. General workflow for using pybind11 with OpenFOAM. The central idea revolves around using

pybind11 to create a Python interpreter that exists alongside the OpenFOAM application. Then, pybind11

allows data to be transferred to and from the Python interpreter, as well as the ability to execute Python

functions and scripts from within OpenFOAM.

The entire process is conceptually depicted in Figure 1. OpenFOAM execution begins as usual, for example

with common tasks involving the creation and initialisation of fields and other auxiliary data structures

(Step 1). At a convenient point, a Python interpreter is created and held in the background (Step 2). The

OpenFOAM execution continues and, when required, it sends data to the Python interpreter (Step 3). An

OpenFOAM command is then called to execute a piece of Python code (Step 4) which invokes the Python

interpreter (Step 5); while the Python interpreter is executing, the OpenFOAM code waits. Finally, the data

is sent back to OpenFOAM, where it can be used in the rest of OpenFOAM workflow (Step 6). Details of

the different steps shown in Figure 1 are given in the subsequent sub-sections.

2.3. Initialising the Python interpreter. To improve readability, it is assumed that all subsequent Open-

FOAM code has invoked the C++ namespace pybind11 as shown:



1 namespace py = pybind11;
✝ ✆

To initialise, embed and expose the Python interpreter within OpenFOAM, the following line is used:

1 py::initialize interpreter();
✝ ✆
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 7

Figure 1. Overview of the interaction between OpenFOAM and Python, via pybind11.

This can be considered a higher level version of the Py Initialise() method from the Python C API and its

location in the code must ensure that it is run only once. After the interpreter is created, it can be interacted

with at any time. The line can be placed anywhere convenient within the OpenFOAM code, for example, in

the createFields.H header file of a solver, in the constructor of a class, or whenever it is needed.

2.4. Transferring data between OpenFOAM and Python. To facilitate data transfer between Open-

FOAM and Python, a Python scope object must be created and stored in OpenFOAM:

1 py::object scope = py::module ::import(" __main__ ").attr(" __dict__ ");
✝ ✆

A dissection this line requires analysing both main and dict in a Python context. According to

the official Python documentation [26], main is the name of the environment where top-level code is run.

It is top-level because it imports all other modules that the program needs. dict , on the other hand,

is a dictionary or other mapping object used to store an object’s (writable) attributes [27]. Therefore, this

line imports the dictionary that contains all the variables defined in the Python interpreter and assigns it to
8 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

the object scope on the C++ side; this C++ scope object can be used to declare variables in the Python

interpreter from C++ or to retrieve variables from Python to the C++ side.

Passing by copy Using this scope object, it is possible to copy the variables from OpenFOAM to Python;

for example, the OpenFOAM scalar variable a can be copied to a new variable in the Python scope, x, as

follows:

1 const scalar a = 2;

2 scope["x"] = a;
✝ ✆

Similarly, the scope object can be used to copy variables from the Python scope to OpenFOAM:

1 const scalar b = scope["y"];
✝ ✆

where it is assumed here that a scalar variable “y” exists in the Python scope before this line is executed.

The examples above show how primitive data types (bool, integer, float, double, etc) can be transferred

between OpenFOAM and Python; this approach can be easily applied to OpenFOAM data containers, such

as List and Field, by passing each element of these containers one-by-one to the Python interpreter to be

processed. The following example code demonstrates how this approach could be used to perform cell-by-cell

calculations using the pressure field p:



1 const scalarField& pI = p.primitiveField();

2 scalarField result(pI.size(), 0.0);

3 forAll(p, cellI)

4 {

5 // Transfer p for cellI to the Python interpreter

6 scope["p"] = pI[cellI];

8 // Perform some calculation in Python as described in Section 2.5}

9 // ...

10

11 // If applicable, retrieve data from Python and store it in an OpenFOAM container

12 // For example:

13 result[cellI] = scope[" result "];

14 }
✝ ✆

Although the approach above works, it may be prohibitively slow for larger fields; the reason for this

is that compiler optimisations on the OpenFOAM side are inhibited, and, similarly, efficient NumPy array
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 9

operators are not utilised on the Python side. An alternative approach is to pass the entire field to the

Python interpreter, process the entire field in Python, and transfer the result field back to OpenFOAM. To

do this, we will use pybind11 wrapper classes as described below.

As stated in the pybind11 documentation, “all major Python types are available as thin C++ wrapper

classes” [28], including object, bool, int, float, str, bytes, tuple, list and others. In general, to

convert C++ types to Python, the method py::cast() is used [15]. For example, if a std::vector<double>

named inputC has been declared and initialised in OpenFOAM/C++, it is possible to create a C++ object

inputPy of type py::array which can be directly transferred to the Python interpreter as a NumPy array.

The following example demonstrates this behaviour:



1 std::vector<scalar> x{10, 20, 30};

2 py::array pyX = py::cast(x);

3 scope ["x"] = pyX;


✝ ✆

where the std::vector is copied to a C++ py::array which can be directly transferred to the Python

scope. The same conversions are allowed when the arrays are multi-dimensional, for example, 2-D ar-

rays; the only difference in that case is that the x variable must be declared as a vector of vectors, i.e.

std::vector<std::vector<type>>. The reverse operation, from a Python NumPy array to a C++

std::vector, uses similar syntax:



1 const py::array resultPy = scope[" result "];

2 const std::vector<scalar> result = resultPy.cast<std::vector<double>>();


✝ ✆

If the NumPy array has two dimensions it would be cast to a std::vector<std::vector<type>>.

In the examples above, std::vector data containers are used on the C++ side; however, as Field and

List containers are the standard in OpenFOAM, we explain here how to convert between these container

types. Essentially, the std::vector containers are used as a intermediate step when transferring field data

between OpenFOAM and Python, as illustrated schematically in Figure 3.

The implication of passing OpenFOAM field data on an element-by-element basis or as an entire field is

examined in the third and final test case in Section 3.3.

Passing by reference Using the approach described above, a copy of the data is created when it is

transferred between C++ and Python; that is to say, when a C++ variable is transferred to the Python

interpreter scope, the C++ data type is copied to a Python data type. As an alternative, it is possible to

exchange data between C++/OpenFOAM and Python by reference rather than as a copy. The approach

is based on the built-in NumPy support for ctypes, which “provides C compatible data types” in Python
10 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

Figure 2. Forward conversion. Passing an OpenFOAM field to the Python interpreter


implies extracting its data to create a C++ std::vector, creating a py::array from it and
transferring it to the Python interpreter

Figure 3. Reverse (backward) conversion. Passing an NumPy array from the Python
interpreter implies transferring it to a py::array in the C++ side, which is later cast to a
std::vector via pybind11 and finally converted back to OpenFOAM field

[29]. The idea here is to introduce minor changes in the original Python code to allow Python to interact

directly with the underlying data in the OpenFOAM C++ code, thus removing the need for data copying

steps. Every OpenFOAM field stores its data in the form of a C array, and a pointer to the first element of

this array can be retrieved using the data (or cdata) functions provided by the underlying UList base class.

Using pybind11, this pointer address is converted to an unsigned integer and sent to the Python interpreter

along with the size/length of the array. From the Python side, this integer address and size are used to create

a NumPy array, which provides a reference to the C array data, without copying data. This can be achieved

in Python as:

1 strain pointer = ctypes.cast(strain address, ctypes.POINTER(ctypes.c double))

2 strain = np.ctypeslib.as array(strain pointer, shape = (SIZE, 6))


✝ ✆

where strain address is the integer address from C++/OpenFOAM and SIZE is the size/length of the array.

Since the arrays created in the Python side are directly manipulating the underlying data of the OpenFOAM

fields, all the modifications done in the Python side are immediately propagated to the OpenFOAM side,

eliminating the need for copying the data. Clearly, care must be taken when using this approach as memory

errors may occur if the address changes on the C++ side without informing Python.

2.5. Executing Python code from OpenFOAM. The command py::eval file(string, scope) where

string is the name of a Python file/script, loads all the entities defined in the Python file, which can then

be consumed using the pybind11 py::exec command. For example, assume the following trivial Python

function exists in the loaded Python script:



1 def double value(number):

2 return 2.0*number
✝ ✆
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 11

This function receives a number as an argument and returns the number × 2. The following C++ code

demonstrates how to use this Python function:



1 // Define scalar ‘a’ in OpenFOAM

2 const scalar a = 2.0;

3 // Transfer OpenFOAM variable ‘a’ to a variable ‘x’ in the Python interpreter

4 scope["x"] = a;

5 // Execute the Python function ‘double value’

6 py::exec("y = double_value (x)", scope);

7 // Transfer the value of the Python variable ‘x’ to the OpenFOAM scalar ‘b’

8 const scalar b = scope["y"];


✝ ✆

• Declares a scalar variable a and initialises its value to 2.0;

• Declares a variable x in the Python interpreter and assigns it the value of OpenFOAM variable a;

• Calls the Python function double value with the Python variable x as an argument, and assigns the

result to the Python variable y;

• Copies the value of the Python y variable to a new OpenFOAM variable b.

Table 1 shows the variables that in the OpenFOAM/C++ and Python scopes, and how their values change

as each line of code is executed in sequence.

Executed OpenFOAM/C++ code OpenFOAM/C++ variables Python variables


const scalar a = 2.0; a = 2.0
scope[“x”] = a; a = 2.0 x = 2.0
py::exec(“y = double value(x)”, scope) a = 2.0 x = 2.0, y = 4.0
const scalar b = scope[“y”]; a = 2.0, b = 4.0 x = 2.0, y = 4.0

Table 1. Evolution of the variables values as each line in the C++ code is executed.

3. Test Cases

This section presents three complementary test cases that demonstrate the methodology proposed in

Section 2:

(1) Python velocity profile boundary condition: An OpenFOAM wrapper boundary condition is

presented, which uses a Python script - supplied at run-time - to define a temporally-spatially varying

velocity profile;

(2) Python heat transfer prototyping solver: A wrapper OpenFOAM heat transfer solver is pre-

sented, which invokes a run-time Python script to solve the governing equations at each time step; this
12 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

case shows how new solvers could be more quickly prototyped using a combination of OpenFOAM

and Python;

(3) Field calculations using Python A wrapper solids4foam [30] mechanical constitutive law is pre-

sented to demonstrate how a volSymmTensorField stress field can be calculated using Python as

a function of a volSymmTensorField strain field. Analytical expressions are compared with Ten-

sorFlow Keras [31] neural networks, with an emphasis placed on efficiency. It is expected that the

approach could be easily adapted to any other field calculation.

These cases and the code to run them are publicly available at https://bitbucket.org/ScimonUCD/pybind11foam/

src/master/.

3.1. Python velocity profile boundary condition. This case demonstrates how the general methodology

presented in Section 2 can be used to create a Python-based boundary condition in OpenFOAM. In this case,

a velocity profile boundary condition is created, where a Python script calculates the patch velocities as a

function of spatial coordinates and time. The boundary condition, named pythonVelocity, is a Dirichlet

condition and hence derives from the fixedValueFvPatchField boundary condition class. The central

concepts of the pythonVelocity boundary condition - related to the private data and updateCoeffs function

- are are described here.

Boundary condition private data Two private data objects are stored in the boundary condition class:

• The name of the Python script (fileName pythonScript ) which is read at run-time;

• The Python interpreter scope object (py::object scope ) used for transferring data from Open-

FOAM to Python, evaluating Python functions, and transferring data back from Python to Open-

FOAM.

Boundary condition updateCoeffs function For each finite volume boundary condition in OpenFOAM

(those derived from fvPatchField), the updateCoeffs function is called as a preliminary step to the linear

solver call. Consequently, the updateCoeffs function is an appropriate location to update patch velocities

as a function of spatial position and time. As per Section 2, there are four essential operations that need to

be included in the custom boundary condition:

• Within the boundary condition constructor, initialise the Python interpreter and load the Python

file;

• Within updateCoeffs, transfer the list of face-centre coordinates and time value to Python;

• Within updateCoeffs, call the Python script to calculate the list of new face velocities;

• Within updateCoeffs, transfer the list of new face velocities back to OpenFOAM.

In the boundary condition constructor, the Python interpreter is initialised and the Python script is read:
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 13


1 // Initialise the Python interpreter

2 py::initialize interpreter();

3 scope = py::module ::import(" __main__ ").attr(" __dict__ ");

5 // Evaluate the Python script to import modules

6 py::eval file(pythonScript , scope );


✝ ✆

Next, in the updateCoeffs function, the patch face-centre coordinate vectors and current time are trans-

ferred to the Python scope:



1 // Convert the face−centre position vectors to a std::vector

2 const vectorField& C = patch().Cf();

3 std::vector<std::vector<scalar>> inputC(C.size());

4 forAll(C, faceI)

5 {

6 inputC[faceI] = std::vector<scalar>(3);

7 for (int compI = 0; compI < 3; compI++)

8 {

9 inputC[faceI][compI] = C[faceI][compI];

10 }

11 }

12

13 // Convert std vector to a C++ NumPy array

14 const py::array inputPy = py::cast(inputC);

15

16 // Transfer the C++ NumPy array to a NumPy array in the Python scope

17 scope [" face_centres "] = inputPy;

18 scope ["time"] = db().time().value();


✝ ✆

The run-time Python script is then called to calculate the new velocities as a function of the face-centre

coordinate vectors (face centres) and the current time (time):



1 py::exec(" velocities = calculate ( face_centres , time )\n", scope );
✝ ✆

The output velocities list exists in the Python scope. Note that the only requirement of the run-time

loadable Python script is that it contains the definition of a function called calculate which accepts two
14 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

arguments, one a NumPy array of vectors and the other a double, and returns a NumPy array of vectors. A

suitable example Python function, included in the accompanying setInletVelocity.py file, is:

1 import numpy as np

3 def calculate(face centres, time):

4 # Initialise result

5 result = np.zeros(shape = face centres.shape)

6 # Calculate values using the x coordinates and time

7 x = face centres[:, 0]

8 result[:, 0] = np.sin(np.pi*time)*np.sin(40*np.pi*x)

10 return result
✝ ✆

where the expression in this case varies the X component of velocity as a function of the X component of the

face-centre coordinate vector (x) and time (t), according to:

u(x, t) = sin(πt) sin(40πx) (1)

The calculated velocities are then retrieved from the Python scope and converted into an OpenFOAM

field:

1 // Convert the Python velocities to a C++ NumPy array

2 const py::array outputPy = scope [" velocities "];

4 // Convert the C++ NumPy array to a C++ std vector

5 const std::vector<std::vector<scalar>> outputC =

6 outputPy.cast<std::vector<std::vector<scalar>>>();

8 // Convert the C++ std vector to an OpenFOAM field

9 vectorField velocities(patch().size());

10 forAll(velocities, faceI)

11 {

12 for (int compI = 0; compI < 3; compI++)

13 {

14 velocities[faceI][compI] = outputC[faceI][compI];

15 }
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 15

16 }
✝ ✆

Finally, the velocity values are set on the patch:



1 fvPatchField<vector>::operator==(velocities);

2 fixedValueFvPatchVectorField::updateCoeffs();
✝ ✆

Verification of the proposed boundary condition The proposed pythonVelocity boundary condition

is verified on the classic cavity tutorial case [32], where it is used to set the velocity on the upper moving

wall patch. The only modifications made to the tutorial were to change the velocity boundary condition for

the moving wall patch in the initial time (in 0/U):



1 movingWall

2 {

3 type pythonVelocity;

4 pythonScript " $FOAM_CASE / setInletVelocity .py";

5 value uniform (0 0 0);

6 }
✝ ✆

where the setInletVelocity.py script is placed in the case directory.

Running the case produces the velocity profile on the movingWall patch at 0.5 s as shown in Figure 4,

where the expected analytical expression is given for comparison. The corresponding velocity streamlines

and pressure distribution are shown in Figure 5.

3.2. Python heat transfer prototyping solver. This case demonstrates one of Python’s main advantages

over C++: fast prototyping. To do this, as an example, the classic laplacianFoam solver is modified such

that a run-time selectable Python script is passed mesh, material and time information and is expected to

calculate the temperature field. In this case, a simple explicit finite difference method is implemented in

Python, however, the approach could be modified to accommodate the prototyping any particular solution

strategy or component of it.

As in the first test case, there are three main components added to the OpenFOAM code: data transfer

from OpenFOAM to Python; execution of the Python code; transfer of the result data back from Python to

OpenFOAM.

Data transfer from OpenFOAM to Python Following the approach in Section 2, OpenFOAM data

is mapped, where relevant, to a corresponding Python NumpPy array data. In this case, two pieces of data

are passed to the Python function:


16 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

Figure 4. Velocity along the top boundary at 0.5 s

Figure 5. Velocity streamlines in m s−1 (left) and pressure field in Pa m3 kg−1 (right) for
the cavity case, where the velocity on the upper wall is prescribed via a Python script.

• A parameter gamma, which encompasses diffusivity, mesh spacing and pseudo-time-step data:

1 scope["gamma"] =

2 scalar

3 (
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 17

4 DT*runTime.deltaT()*Foam::pow(max(mesh.deltaCoeffs()), 2)

5 ).value();
✝ ✆

• The temperature field, including boundary condition values, which are assumed here to be fixedValue-

type (Dirichlet) conditions:


1 // Set boundary cell values to be equal to the boundary values as we

2 // will use a finite difference method in the Python script

3 // These boundary cells will be the boundary nodes in the finite

4 // difference code

5 scalarField& TI = T.primitiveFieldRef();

6 forAll(T.boundaryFieldRef(), patchI)

7 {

8 const labelUList& faceCells =

9 mesh.boundary()[patchI].faceCells();

10 forAll(faceCells, faceI)

11 {

12 const label cellID = faceCells[faceI];

13 TI[cellID] = T.boundaryFieldRef()[patchI][faceI];

14 }

15 }

16

17 // Convert T field to C++ std vector

18 std::vector<scalar> inputC(TI.size());

19 forAll(TI, cellI)

20 {

21 inputC[cellI] = TI[cellI];

22 }

23

24 // Convert std vector to NumPy array

25 const py::array inputPy = py::cast(inputC);

26

27 // Assign inputs to Python scope

28 scope["T"] = inputPy;
✝ ✆
18 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

Perform Python calculations As in the first test case, the Python script is selected at run-time,

however, in this case it is expected that the provided Python script contains a calculate function, which

takes two arguments - the temperature field T with boundary conditions, and the gamma variable - and returns

the new temperature field. The function is called in the OpenFOAM solver as:

1 py::exec("T = calculate (T, gamma )\n", scope);
✝ ✆

The Python function in this case employs a simple steady-state explicit finite difference approach to solve

the Laplace equation:



1 def calculate(T, gamma):

3 # Get number of cells in x and y directions

4 N = T.shape[0]

5 Nx = np.sqrt(N).astype(int)

6 Ny = Nx

8 # Use explicit finite difference method to update the non−boundary cells

9 for i in range(1, Nx − 1):

10 for j in range(1, Ny − 1):

11 T[i*Ny + j] = \

12 gamma*(T[i*Ny + j + 1] + T[i*Ny + j − 1] \

13 + T[(i + 1)*Ny + j] + T[(i − 1)*Ny + j] \

14 − 4*T[i*Ny + j]) + T[i*Ny + j]

15

16 return T
✝ ✆

Data transfer from Python to OpenFOAM The temperature field results are then extracted from

the Python scope back to the OpenFOAM solver as:



1 // Transfer Python NumPy array to C++ NumPy array

2 const py::array outputPy = scope["T"];

4 // Convert C++ NumPy array to C++ std vector

5 const std::vector<scalar> outputC =

6 outputPy.cast<std::vector<scalar>>();

7
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 19

8 // Convert C++ std vector to OpenFOAM field

9 forAll(TI, cellI)

10 {

11 TI[cellI] = outputC[cellI];

12 }
✝ ✆

Results The proposed prototyping solver is demonstrated on a steady-state heat conduction problem with

a 2-D square geometry (0.1 × 0.1 m) and 20 × 20 cells - the geometry and mesh from the cavity tutorial.

The diffusivity is 4 × 10−5 m2 s−1 . The left, bottom, and right boundaries are set to 273 K, while the top

boundary is set to 373 K. The Python script is specified in constant/transportProperties as:



1 pythonScript " $FOAM_CASE / calculateT .py";
✝ ✆

where calculateT.py is the Python explicit finite difference code shown above.

The final converged temperature field is shown in Figure 6(a). For comparison, the temperature along a

vertical line from the centre of the bottom patch to the centre of the top patch are compared with the results

as calculated by laplacianFoam (Figure 6(b)). As expected the predictions are similar, with the differences

primarily attributed to the Python finite difference code enforcing boundary conditions in the cell-centres of

cells adjacent to the boundaries.

Figure 6. Steady-state temperature distribution as calculated by a Python finite difference


code embedded in an OpenFOAM solver (left) and temperature profile along a vertical line
from the centre of the bottom patch to the centre of the top patch, compared with the
predictions from laplacianFoam (right).

3.3. Field calculations using Python. The purpose of this final test case is to demonstrate how to perform

field calculations using the embedded Python interpreter; specifically, this case shows how this can be done

using machine learning models implemented in TensorFlow/Keras, scikit-learn, and Python in general. A
20 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

solid mechanics problem is chosen, where the stress tensor in each cell is calculated as a function of the

displacement gradient via a run-time selectable constitutive mechanical law. This approach could be readily

applied to similar field calculations, such as the calculation of turbulent quantities, thermo-physical property

fields, or even for discrete differential operators. The case (Figure 7) consists of a plate, with a circular hole

in the centre, loaded by uniform tension tx = 1 MPa on the right boundary, while symmetry conditions

are applied to the left and bottom boundaries. Two quadrilateral meshes were considered: a coarse mesh

with 1 000 cells (Figure 7), and a finer mesh with 400 000 cells. All the simulations were solved for 1 time

step, small strains were assumed, and the inertia and gravity terms were neglected. A Hookean linear elastic

material is assumed with a Young’s modulus of E = 200 GPa and a Poisson’s ratio of ν = 0.3.

Figure 7. Geometry of the spatial computational domain for the flat plate with a circular
hole (a = 0.5 m, b = 2 m, E = 200 GPa, ν = 0.3, tx = 1MPa).

At each outer iteration within a time-step, the stress tensor σ in each cell is calculated as a function of

the strain tensor ǫ, according to Hooke’s law as

σ = 2µǫ + λ tr (ǫ) I (2)

where the strain tensor ǫ is the symmetric component of the displacement gradient ∇d:

1h T
i
ǫ= ∇d + (∇d) (3)
2

For demonstration purposes, the calculation of the stress tensor in each cell (Equation 2) is performed

using seven alternative approaches:


RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 21

(1) solids4foam reference case: this simulation uses the native solids4foam/OpenFOAM implemen-

tation and does not use Python.

(2) Analytical expression in Python (Analytical-Python): the strain tensor is passed to the Python

interpreter; the stress is then calculated in Python as per Equation 2 and returned to OpenFOAM;

in this case, no machine learning methods are used.

(3) Neural network base case (NN-Base): as per approach 2, the strain is passed to Python, but

in this case the stress is called using an artificial neural network created in Keras, which has been

trained on data from Equation 2. The prediction method uses the built-in .predict Keras function.

Approaches 4 to 7 use the same trained neural network as in approach 3, however, alternative neural

network prediction methods are examined.

(4) Neural network functional model (NN-Functional): the neural network is constructed using the

Keras Functional API, as opposed to the Sequential model used in approach 3 [31].

(5) Neural network with eager mode disabled (NN-Eager-Disabled): the so-called eager execution

mode [33] of TensorFlow – which Keras is built-on – is disabled.

(6) Neural network using NumPy (NN-NumPy): the Keras neural network is fully translated to

pure NumPy array calculations, breaking the dependency with Keras and TensorFlow.

(7) Neural network using NumPy-Numba [34] (NN-NumPy-Numba): Numba is an open source

just-in-time compiler that translates a subset of Python and NumPy code into fast machine code

[35]. Here, Numba is applied to approach 6.

Each of these seven approaches are implemented in their own python file.py script, loaded at run-time.

Details of these Python implementations are given in Appendix A.

The hole-in-a-plate case is solved twice using each of the seven approaches above: in the first solution, the

strain tensor is passed to the Python interpreter on a cell-by-cell basis, whereas in the second solution, the

entire OpenFOAM strain field (all cells) is transferred in one go to the Python interpreter, as discussed in

Section 2.4.

Performance comparison The predicted von Mises stress distribution for the reference solids4foam

case is shown in Figure 8(a), while the predicted xx component of the stress tensor σ xx along the vertical

line x = 0 is shown for all seven approaches in Figure 8(b). At this scale, the prediction from all approaches

closely agree. Table 2 lists the L∞ norm and average L2 norm of the differences with the reference case for

the displacement magnitude and von Mises stress fields; the maximum errors in the von Mises stress (< 800

MPa) and displacement magnitude (< 3e-9 m) are small in comparison to the maximum von Mises stress (2.7

MPa) and maximum displacement magnitudes (13 µm) in the reference solution. In addition, the predictions
22 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

are the same regardless of whether the data is transferred on a cell-by-cell basis or by the entire field, and as

such the data is only presented once for each approach.

xx along x = 0 m
3.0

2.5
(in MPa)

2.0
xx

1.5

1.0

0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0


y-coordinate (m)
(a) σxx stress from the top of the hole to the top boundary. (b) Von Mises stress distribution for the
solids4foam reference case.

Figure 8. Stress predictions for the hole-in-a-plate case

Von Mises Stress Displacement


(in Pa) (in m)
L2 L∞ L2 L∞
solids4foam - - - -
Analytical-Python 7.12 775 4.91e-11 2.50e-9
NN-Base 7.42 788 5.60e-11 2.70e-9
NN-Functional 7.70 790 5.87e-11 2.80e-9
NN-Eager-Disabled 7.42 788 5.60e-11 2.70e-9
NN-NumPy 7.42 781 5.62e-11 2.70e-9
NN-NumPy-Numba 7.42 781 5.62e-11 2.70e-9

Table 2. L∞ norm and mean L2 norm between the predictions from approaches 2-6 with
the reference case, where the maximum stress in the reference case is 2.7 MPa and the
maximum displacement magnitude is 13 µm.

As expected the predictions are insensitive to the approach, however, there is a wide variation in the

timings between the implementations. Table 3 shows the execution times for all seven implementations on

the coarse (1 000 cells) and fine (400 000 cells) meshes, where the ‘Ratio’ is given for timing relative to the

reference case.

The first insight to be gained is that passing the data as entire fields is significantly faster than on a

cell-by-cell basis; for the analytical Python method (approach 2), the entire field approach is almost three

times faster than the cell-by-cell approach, for both coarse and fine meshes. The difference is even greater
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 23

Coarse mesh Fine mesh


Cell Cell
Entire Entire
by Ratio Ratio by Ratio Ratio
Field Field
Cell Cell
solids4foam < 1* - < 1* - 490* - 490* -
Analytical-Python 8 37.6 3 14.2 1459 3.0 531 1.1
NN-Base 2189 10943.6 18 91.9 >6h > 45 576 1.2
NN-Functional 117 586.1 7 34.3 >6h > 45 587 1.2
NN-Eager-Disabled 87 436.6 7 34.2 >6h > 45 584 1.2
NN-NumPy 20 97.5 6 28.0 791 1.6 781 1.6
NN-NumPy-Numba 20 101.7 6 32.0 3574 7.3 837 1.7

Table 3. Execution time for all the evaluated cases in s (unless stated otherwise), where
timings are rounded to the nearest second. The ‘Ratio’ gives the ratio of the time for the given
approach to the time for the reference case. *The cell-by-cell and entire field designations
do not apply to the reference solids4foam calculations.

for the neural network approaches, with the entire field approach being orders of magnitude faster than the

cell-by-cell approach.

Among the Python-based implementations, the analytical expression (approach 2) is the most efficient,

introducing an overhead as small as 8% when the data is passed as an entire field. This matches the expected

behaviour, considering that the analytical expression involves less floating point operations than any of the

examined neural networks. Comparing the neural network approaches, the neural network base case, the

functional model, and the disabled eager methods (approaches 3-5) all perform similarly on the fine mesh,

adding approximately an 18% overhead relative to the reference solids4foam case. Similar trends are seen

for the coarse mesh, although the overhead is significantly greater (>600%); from this, we can conclude that

there is a fixed overhead associated with loading a neural network (and even just the Python interpreter),

but this has a lesser effect as the cell count increases.

Examining the final two approaches, which are the non-Keras-based neural networks (NumPy and NumPy-

Numba-based), they show a minor advantage for the coarse mesh but actually slow down the solution for the

finer mesh.

Passing fields by reference As a final attempt to reduce the overhead of using the Python interpreter,

we will examine the effect of transferring data by reference (i.e. by address) rather than by copying the data,

as described in Section 2.4. Table 4 compares the results for the pass-by-copy approach presented in the

previous section and the pass-by-reference approach, where results are only shown for the fine mesh using

the entire field approach. The pass-by-reference approach can be seen to produce a significant speedup in

the timings. The analytical Python method (approach 2) exhibits essentially the same execution time as the

reference solids4foam case. Similarly, the base neural network approach only shows an overhead of less than

4%, while the slowest Keras-based neural network (functional API method) only increases the execution time
24 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

by 7.3%. As in the pass-by-copy approach, the NumPy and NumPy-Numba neural networks methods show

the slowest neural network performance.

Execution Time (in s)


Pass by Copy Pass by Reference
solids4foam 491* 491*
Analytical-Python 531 495
NN-Base 576 509
NN-Functional 587 526
NN-Eager-Disabled 584 505
NN-NumPy 781 767
NN-NumPy-Numba 837 878

Table 4. Timing results for the pass-by-reference approaches. *The cell-by-cell and entire
field designations do not apply to the native solids4foam calculations.

4. Conclusion

This article introduces a general approach for running Python code in OpenFOAM. This is achieved with

the pybind11 header library, which is used to create an instance of the Python interpreter and for exchanging

data between OpenFOAM and Python. The three examples demonstrate the feasibility and efficiency of

the presented approach, where the use of machine learning models is demonstrated in the final case. When

dealing with the calculation of fields, passing data cell-by-cell is compared with passing data via the entire

field. In addition, when passing the entire field, passing-by-copy is compared with pass-by-reference. The

key findings of the article:

• It is straight-forward to implement functionality such as boundary conditions, solution algorithms

and material models in Python via the pybind11 interpreter approach;

• The most computationally inexpensive approach is to pass data between OpenFOAM and Python

as entire fields by reference, with an overhead of less than a few percent for the best performing

feed-forward neural networks with 266 weights, on a mesh with 400 000 cells;

• Passing data between OpenFOAM and Python by copy on a cell-by-cell basis can be prohibitively

expensive for larger fields, potentially increasing the run-time by multiple orders of magnitude;

• The use of pure NumPy neural networks or Numba-enhanced NumPy neural networks show minimal

benefits over the native TensorFlow/Keras Python implementations, and are in fact slower in most

cases.

Although not been demonstrated in article, the presented approach runs in parallel via the standard Open-

FOAM MPI approach without any changes; in that case, each processor has its own local copy of the Python

interpreter. It is hoped that the methods presented in this article will expand the universe of Python-based
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 25

solutions applicable to OpenFOAM, especially those coming from the rapidly evolving machine learning field.

Future work will explore more complex problems related to elasto-plasticity, requiring machine learning mod-

els such as recurrent neural networks.

5. Acknowledgements

Financial support is gratefully acknowledged from the Irish Research Council through the Laureate pro-

gramme, grant number IRCLA/2017/45.

Additionally, the authors want to acknowledge project affiliates, Bekaert, through the Bekaert University

Technology Centre (UTC) at UCD (www.ucd.ie/bekaert), and I-Form, funded by Science Foundation Ireland

(SFI) Grant Number 16/RC/3872, co-funded under European Regional Development Fund and by I-Form

industry partners.

The authors wish to acknowledge the DJEI/DES/SFI/HEA Irish Centre for High-End Computing (ICHEC)

for the provision of computational facilities and support (www.ichec.ie).


26 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

Appendix A. Python scripts for the linear Hookean laws implemented in section 3.3

The run-time loaded python file.py for each of the six Python methods (approaches 2 to 7) are given

below, along with any additional, necessary code.

Analytical expression in Python (Analytical-Python)



1 def predict(strain tensor):

2 stress = np.zeros([strain tensor.shape[0], 6])

3 stress[:, 0] = 2 * lame 2 * strain tensor[:, 0] \

4 + lame 1 * (strain tensor[:, 0] \

5 + strain tensor[:, 1] + strain tensor[:, 2])

6 stress[:, 1] = 2 * lame 2 * strain tensor[:, 1] \

7 + lame 1 * (strain tensor[:, 0] \

8 + strain tensor[:, 1] + strain tensor[:, 2])

9 stress[:, 2] = 2 * lame 2 * strain tensor[:, 2] \

10 + lame 1 * (strain tensor[:, 0] \

11 + strain tensor[:, 1] + strain tensor[:, 2])

12 stress[:, 3] = 2 * lame 2 * strain tensor[:, 3]

13 stress[:, 4] = 2 * lame 2 * strain tensor[:, 4]

14 stress[:, 5] = 2 * lame 2 * strain tensor[:, 5]

15 return stress
✝ ✆

where lame 1 and lame 2 are the Lamé’s constant and shear modulus, respectively.

Neural network base case (NN-Base) In this case, the mapping between stress and strain from

equation (2) is provided by a neural network previously trained on Keras. This neural network has two

hidden layers with 20 and 6 feed-forward nodes, respectively. The activation function of the first hidden layer

is ReLu and the other is linear. The entire stress calculation process for a material point is summarised in

figure 9. At the training phase, both the strain and stress tensors in the training set are scaled into the range

[0, 1] using a linear scaler from the Scikit-Learn [36] Python library and fed to the neural network which

calculates the corresponding scaled stress tensor which is scaled back to its original range.

Therefore, in order to use the resulting neural network, the same scalers need to be available in Python. The

strategy used here was to serialise them once the training phase was completed, place them in the OpenFOAM

working directory and load (or deserialise) them in the embedded Python interpreter in OpenFOAM through

the python file.py. This is done using the Joblib Python library as shown in the next code:

1 from joblib import load
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 27

Figure 9. Linear elastic Hookean law based on a neural network. The components of the
strain tensor (ǫ) are scaled to the range [0, 1] (ǭ) and fed to a neural network. It calculates
the corresponding scaled stress tensor (σ̄) which is scaled back to its natural range (σ).

2 x scaler = load(’x_scaler . joblib ’)

3 y scaler = load(’y_scaler . joblib ’)


✝ ✆

where x scaler.joblib and y scaler.joblib are the strain and stress scalers, respectively.

As in the previous case, the strain-stress mapping is provided by a predict function in the Python code,

given by:

1 def predict(x new):

2 x new scaled = x scaler.transform(x new)

3 x new scaled reshaped = x new scaled.reshape(1, x new.shape[0], 6)

4 prediction scaled = model.predict(x new scaled reshaped)

5 prediction output scaled = prediction scaled.reshape(x new.shape[0], 6)

6 prediction = y scaler.inverse transform(prediction output scaled)

7 return prediction
✝ ✆

In this case, the built-in Keras .predict method is used to perform the inferences from the neural network.

Neural network functional model (NN-Functional) When the predictions are performed on a cell-

by-cell basis, the .predict method is located within a for loop that iterates over the cells, which is discouraged

[37]. For this reason, the previous case was revisited to modify the predict function. Instead of using Keras

.predict, another neural network is built with the same layers of the previous model using the Functional

API from Keras and the inferences are performed by a direct call to the model, as suggested by [37] and

shown in the next code:



1 def predict(x new):
28 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

2 x new scaled = x scaler.transform(x new)

3 x new scaled reshaped = x new scaled.reshape(1, x new.shape[0], 6)

4 prediction scaled = model(x new scaled reshaped, training = False)

5 prediction output scaled = tf.reshape(prediction scaled, [x new.shape[0], 6])

6 prediction = y scaler.inverse transform(prediction output scaled)

7 return prediction
✝ ✆

Neural network with eager mode disabled (NN-Eager-Disabled) TensorFlow’s eager execution

is an imperative programming environment that evaluates operations immediately, without building graphs:

operations return concrete values instead of constructing a computational graph to run later [33]. The

following line disables eager execution, as suggested by [37]:



1 tf.compat.v1.disable eager execution()
✝ ✆

Then, another neural network is created and loaded with the parameters used in the previous networks

with the code:



1 model = Sequential()

2 model.add(Dense(units = 20, kernel initializer = ’he_normal ’,

3 activation = ’relu ’, input shape = (None, 6)))

4 model.add(Dense(units = 6, kernel initializer = ’he_normal ’,

5 activation = ’linear ’))

6 model.compile(optimizer = Adam(lr = 0.01), loss = ’mse ’)

7 model.load weights("DNN.h5")
✝ ✆

where DNN.h5 is the file of the neural network used in the base case. The prediction step to calculate the

stress then procedures the same as in approach 2.

Neural network using NumPy (NN-NumPy) The same neural network presented in the previous

cases was converted to NumPy code using the Python library keras-konverter [38]. First, the parameters of

the network are loaded:



1 wb = np.load(’DNN_weights .npz ’, allow pickle = True)

2 w, b = wb[’wb’]

3 w0, w1 = np.array(w[0], dtype = np.float64), np.array(w[1], dtype = np.float64)

4 b0, b1 = np.array(b[0], dtype = np.float64) , np.array(b[1], dtype = np.float64)


✝ ✆

And the predictions from the neural network are given by:
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 29


1 def neural prediction(x, w0, w1, b0, b1):

2 l0 = x.dot(w0) + b0

3 l0 = np.maximum(0, l0)

4 l1 = l0.dot(w1) + b1

5 return l1
✝ ✆

which requires the predict function to be changed to:



1 def predict(x):

2 x = x scaler.transform(x)

3 x = np.array(x, dtype = np.float64)

4 prediction output scaled = neural prediction(x, w0, w1, b0, b1)

5 prediction = y scaler.inverse transform(prediction output scaled)

6 return prediction
✝ ✆

Neural network using NumPy-Numba (NN-NumPy-Numba) Finally, this case aimed to speed

execution up by using Numba on the previous NumPy based implementation. The main difference with

the previous case is that Numba is imported and a @jit(nopython = True) decorator is added to the

neural prediction function:



1 import numba

2 from numba import jit

4 @jit(nopython = True)

5 def neural prediction(x, w0, w1, b0, b1):

6 l0 = x.dot(w0) + b0

7 l0 = np.maximum(0, l0)

8 l1 = l0.dot(w1) + b1

9 return l1
✝ ✆

where the prediction step is the same as in the previous approach.

References

[1] A. Sharma, Object-oriented Programming with C++. Pearson Education India, 2014.

[2] B. Stroustrup, Programming: principles and practice using C++. Pearson Education, 2014.

[3] H. G. Weller, G. Tabor, H. Jasak, and C. Fureby, “A tensorial approach to computational continuum mechanics using

object orientated techniques,” Computers in Physics, vol. 12, pp. 620–631, 1998.
30 SIMON RODRIGUEZ1,∗ AND PHILIP CARDIFF1

[4] W. McKinney, Python for data analysis: data wrangling with pandas, NumPy, and IPython, 2nd ed. Sebastopol, California:

O’Reilly Media, Inc, 2018, oCLC: ocn959595088.

[5] “PYPL PopularitY of Programming Language index.” [Online]. Available: https://pypl.github.io/PYPL.html

[6] “PyFoam.” [Online]. Available: https://openfoamwiki.net/index.php/Contrib/PyFoam#Short description

[7] “pythonFlu.” [Online]. Available: https://openfoamwiki.net/index.php/Contrib/pythonFlu

[8] CyrilleBonamy, “fluidfoam.” [Online]. Available: https://github.com/fluiddyn/fluidfoam

[9] “Owls.” [Online]. Available: https://github.com/greole/owls

[10] “swak4Foam.” [Online]. Available: https://openfoamwiki.net/index.php/Contrib/swak4Foam#Contents

[11] R. Maulik, D. Fytanidis, B. Lusch, V. Vishwanath, and S. Patel, “Pythonfoam: In-situ data analyses with openfoam and

python,” arXiv preprint arXiv:2103.09389, 2021.

[12] A. Weiner, D. Hillenbrand, H. Marschall, and D. Bothe, “Data-Driven Subgrid-Scale Modeling for Convection-Dominated

Concentration Boundary Layers,” Chemical Engineering & Technology, vol. 42, no. 7, pp. 1349–1356, Jul. 2019. [Online].

Available: https://onlinelibrary.wiley.com/doi/10.1002/ceat.201900044

[13] A. Weiner, “Running PyTorch models in OpenFOAM – basic setup and examples.” [Online]. Available:

https://ml-cfd.com/2020/12/29/running-pytorch-models-in-openfoam-basic-setup-and-examples/

[14] Anderluh and Jasak, “Like OpenFOAM, but Python,” in 16th OpenFOAM Workshop, University College Dublin (Online),

Dublin, Ireland, Jun. 2021.

[15] W. Jakob, J. Rhinelander, and D. Moldovan, “pybind11 – seamless operability between c++11 and python,” 2017,

https://github.com/pybind/pybind11.

[16] D. Kochkov, J. A. Smith, A. Alieva, Q. Wang, M. P. Brenner, and S. Hoyer, “Machine learning–accelerated computational

fluid dynamics,” Proceedings of the National Academy of Sciences, vol. 118, no. 21, 2021.

[17] O. Obiols-Sales, A. Vishnu, N. Malaya, and A. Chandramowliswharan, “CFDNet: a deep learning-based accelerator for

fluid simulations,” in Proceedings of the 34th ACM International Conference on Supercomputing. Barcelona Spain: ACM,

Jun. 2020, pp. 1–12. [Online]. Available: https://dl.acm.org/doi/10.1145/3392717.3392772

[18] D. Pandya, B. Dennis, and R. Russell, “A computational fluid dynamics based artificial neural network

model to predict solid particle erosion,” Wear, vol. 378-379, pp. 198–210, May 2017. [Online]. Available:

https://linkinghub.elsevier.com/retrieve/pii/S0043164816303702

[19] D. W. Abueidda, S. Koric, N. A. Sobh, and H. Sehitoglu, “Deep learning for plasticity and thermo-

viscoplasticity,” International Journal of Plasticity, vol. 136, p. 102852, Jan. 2021. [Online]. Available:

https://linkinghub.elsevier.com/retrieve/pii/S0749641920302096

[20] C. Wang, L.-y. Xu, and J.-s. Fan, “A general deep learning framework for history-dependent response prediction based on

UA-Seq2Seq model,” Computer Methods in Applied Mechanics and Engineering, vol. 372, p. 113357, Dec. 2020. [Online].

Available: https://linkinghub.elsevier.com/retrieve/pii/S0045782520305429

[21] J. Ling, A. Kurzawski, and J. Templeton, “Reynolds averaged turbulence modelling using deep neural networks with

embedded invariance,” Journal of Fluid Mechanics, vol. 807, pp. 155–166, 2016.

[22] X. Morales, J. Mill, K. A. Juhl, A. Olivares, G. Jimenez-Perez, R. R. Paulsen, and O. Camara, “Deep Learning Surrogate

of Computational Fluid Dynamics for Thrombus Formation Risk in the Left Atrial Appendage,” in Statistical Atlases

and Computational Models of the Heart. Multi-Sequence CMR Segmentation, CRT-EPiggy and LV Full Quantification

Challenges, M. Pop, M. Sermesant, O. Camara, X. Zhuang, S. Li, A. Young, T. Mansi, and A. Suinesiaputra, Eds.
RUNNING PYTHON CODES IN OPENFOAM USING PYBIND11 31

Cham: Springer International Publishing, 2020, vol. 12009, pp. 157–166, series Title: Lecture Notes in Computer Science.

[Online]. Available: http://link.springer.com/10.1007/978-3-030-39074-7 17

[23] R. Maulik, H. Sharma, S. Patel, B. Lusch, and E. Jennings, “Deploying deep learning in openfoam with tensorflow,” in

AIAA Scitech 2021 Forum, 2021, p. 1485.

[24] Tobias Hermann, “frugally-deep.” [Online]. Available: https://github.com/Dobiasd/frugally-deep

[25] “[QUESTION]. Crash when importing NumPy manually Issue #2485 - pybind/pybind11.” [Online]. Available:

https://github.com/pybind/pybind11/issues/2485

[26] “ main — Top-level code environment. Python 3.10.0 documentation.” [Online]. Available: https://docs.python.org/3/

library/ main .html

[27] “Built-in Types. Python 3.10.0 documentation.” [Online]. Available: https://docs.python.org/3/library/stdtypes.html?

highlight= dict #built-in-types

[28] “Python types - pybind11 documentation.” [Online]. Available: https://pybind11.readthedocs.io/en/stable/advanced/

pycpp/object.html

[29] “ctypes — A foreign function library for Python. Python 3.10.0 documentation.” [Online]. Available: https:

//docs.python.org/3/library/ctypes.html

[30] P. Cardiff, A. Karač, P. De Jaeger, H. Jasak, J. Nagy, A. Ivanković, and Ž. Tuković, “An open-source finite volume toolbox

for solid mechanics and fluid-solid interaction simulations,” arXiv preprint arXiv:1808.10736, 2018.

[31] F. Chollet et al., “Keras,” https://keras.io, 2015.

[32] “CFD.Direct.com. OpenFOAM v9 User Guide: 2.1 lid-driven cavity flow.” [Online]. Available: https://cfd.direct/

openfoam/user-guide/v9-cavity/#x5-40002.1

[33] “Eager execution — TensorFlow Core.” [Online]. Available: https://www.tensorflow.org/guide/eager

[34] S. K. Lam, A. Pitrou, and S. Seibert, “Numba: A llvm-based python jit compiler,” in Proceedings of the Second Workshop

on the LLVM Compiler Infrastructure in HPC, 2015, pp. 1–6.

[35] “Numba: A high performance python compiler.” [Online]. Available: https://numba.pydata.org/

[36] F. Pedregosa, G. Varoquaux, A. Gramfort, V. Michel, B. Thirion, O. Grisel, M. Blondel, P. Prettenhofer, R. Weiss,

V. Dubourg, J. Vanderplas, A. Passos, D. Cournapeau, M. Brucher, M. Perrot, and E. Duchesnay, “Scikit-learn: Machine

learning in Python,” Journal of Machine Learning Research, vol. 12, pp. 2825–2830, 2011.

[37] “[QUESTION]. model.predict is much slower on tf 2.1+ · issue #40261.” [Online]. Available: https://github.com/

tensorflow/tensorflow/issues/40261

[38] “keras-konverter.” [Online]. Available: https://pypi.org/project/keras-konverter/

You might also like