The DREAM Python interface is a Python module which allows you to directly call certain functions in DREAM C++ library. The interface is designed to allow real-time monitoring of DREAM simulations using Python.
Page overview
Compilation of the dreampyface
module is disabled by default, due to that
it requires special compilation settings which can slightly slow down
simulations. Enabling compilation of the module is however as easy as running
CMake with the flag -DDREAM_BUILD_PYFACE=YES
(as usual, done from the
build
directory):
cmake .. -DDREAM_BUILD_PYFACE=YES
The next time make
is run, a subdirectory build/dreampyface/cxx
will
be created with the file libdreampy.so
in it. This file is the actual
low-level Python module, and could in principle be imported in your script as
is. We however strongly recommend that you use the higher-level Python API
located directly under dreampyface
in the DREAM root directory, which wraps
the low-level API and provides a more user-friendly interface.
Note
When enabling compilation of dreampyface
, you should expect a slight
performance drop in your simulations, regardless of whether you run them
through the Python module or through the usual C++ interface dreami
.
The reason for this is that certain optimizations (use of jump instructions
with relative addresses) are not possible for dynamically linked libraries,
which libdreampy.so
necessarily is. Since the DREAM and FVM libraries
will be included in libdreampy.so
, they must also be compiled with
so-called position-independent code. This in turn means that since we do
not want to generate two sets of libraries, also dreami
will be compiled
with position-independent code.
The first step of using the module is to import it in your Python script.
Assuming that both the dreampyface
directory and
build/dreampyface/cxx/libdreampy.so
is in your Python path, importing the
interface is as simple as
import dreampyface
If the above-mentioned directory and file are not in your Python path, you will
need to add them before importing dreampyface
, for example at the top of
your script using
import sys
# Root directory of your DREAM installation
DREAMPATH = '/path/to/DREAM'
sys.path.append(DREAMPATH)
sys.path.append(f"{DREAMPATH}/build/dreampyface/cxx")
The following script illustrates the two most basic ways of running a DREAM
simulation using the dreampyface
API:
import dreampyface
from DREAM import DREAMSettings
ds = DREAMSettings()
# Setup simulation...
...
# Run simulation
do = dreampyface.run(ds)
# ...or
sim = dreampyface.Simulation(ds)
do = sim.run()
These two approaches are essentially equivalent—under the hood,
dreampyface.libdreampy.run()
effectively creates a Simulation
object and calls dreampyface.Simulation.Simulation.run()
on it. The
primary difference between the two is that when manually creating the
Simulation
object, you will be able to retrieve information about the
simulation before running it.
Whenever you have access to a dreampyface.Simulation.Simulation
object (which can be after manually creating it, or in a
callback function) you will be able to query DREAM
about details of the simulation. The
dreampyface.Simulation.Simulation
class has a number of methods for
getting information about e.g. simulation length, progress, unknown quantities
being evolved, solution data etc. The following example illustrates one possible
use case, although there are plenty of ways in which the class can be used. For
a complete and up-to-date list of available methods, check the auto-generated
documentation for dreampyface.Simulation.Simulation
.
import dreampyface
...
sim = dreampyface.Simulation(ds)
unknowns = sim.unknowns.getInfo()
for name, info in unknowns.items():
print('{:12s} {:8d} {}'.format(name, info['nelements'], info['description']))
DREAM can be instructed to call Python functions on certain events in the code. At the moment, two types of events are supported: whenever a time step is completed, and whenever a non-linear solver iteration is completed.
Registering a callback function (i.e. a function to be called on either of the
aforementioned events) can be done in two ways: either by calling one of the
functions with names starting with register_callback_
, or the onXXX
functions of the dreampyface.Simulation.Simulation
class.
import dreampyface
...
# Procedural interface
dreampyface.register_callback_iteration_finished(lambda _ : print('Iteration finished'))
dreampyface.register_callback_timestep_finished(timestepFinished)
# Object-oriented interface
sim = dreampyface.Simulation(ds)
sim.onIteration(lambda _ : print('Iteration finished'))
sim.onTimestep(timestepFinished)
# Callback function for when a time step has been completed...
# ('ptr' is a pointer to the C++ Simulation object)
def timestepFinished(ptr):
s = dreampyface.Simulation(ptr)
print('Time: {} / {}'.format(s.getCurrentTime(), s.getMaxTime()))
The Python bindings for libdream are written using both C++ and Python. The C++ code uses the CPython C API to provide a number of functions to the Python code for interacting with a C++ Simulation object (which is the “mother” object in any DREAM simulation and provides access to every part of the simulation).
The interface contains C++ code for translating a Python dictionary into a
DREAM Settings
object, as well as an SFile
class for reading/writing a
Python dictionary as if it were a data file.