DREAM simulations can be executed directly from your Python code using the
runiface()
function. This function takes either a DREAMSettings
object,
or the name of a file containing such an object, as input and executes the
DREAMi program.
Page overview
When using runiface()
, the DREAM Python interface must be able to locate
the dreami
executable. Normally this is done automatically when the DREAM
Python package is loaded, but if you have an unusual organization of the files
this may fail. In such a case, you can explicitly specify the path to the
dreami
executable by setting the DREAMPATH
environment variable.
If you are using the bash shell, add the following line to the file
~/.bashrc
:
export DREAMPATH="/path/to/DREAM"
and make sure to replace /path/to/DREAM
with the actual path to your main
DREAM directory. The DREAM Python interface will then assume that that directory
contains the path build/iface
, which in turn should contain the executable
dreami
.
To run DREAM directly from a Python script, simply call runiface()
with a
DREAMSettings
object or the name of an HDF5 file containing such an object:
ds = DREAMSettings()
...
do = runiface(ds)
The runiface()
function will call DREAM and load the resulting output file
into a DREAMOutput
object, which is returned by the function. By default,
DREAM is instructed to store the output in a temporary file which is removed
immediately after being loaded by runiface()
. If you want to keep the output
file, you must manually specify the name of the output file to generate:
# Stores output in 'output.h5', loads the file and returns
# a DREAMOutput object when finished.
do = runiface(ds, 'output.h5')
The full syntax of the function is
do = runiface(ds, 'output.h5', quiet=True, timeout=3600, nthreads=4)
Note
Internally, runiface()
saves the DREAMSettings
to a temporary file
and runs DREAM with the name of that file as argument. Temporary files with
weird names may therefore show up while running the code. The files are
automatically removed after DREAM finishes.
DREAM writes some information to stdout
and stderr
while running. By
default runiface()
forwards this information from DREAM to the terminal. If
you want to suppress this output, you can provide the argument quiet=True
to the function:
do = runiface(ds, quiet=True)
When running many simulations it may be difficult to keep track of whether any particular simulation has stalled. It is therefore possible to impose a timeout on the process which causes it to be killed if it runs for longer than a specified number of seconds:
do = runiface(ds, timeout=True)
The Intel MKL linear solver allows for parallelization of the matrix inversion
step in each iteration. It has been observed that in certain cases, this may
cause separate DREAM processes to interfere with each other and significantly
slow each other down
(see Issue #231).
To avoid this, one can specify the maximum number of threads which each process
is allowed to occupy using the nthreads
argument:
do = runiface(ds, nthreads=4)
A parallel version of runiface()
has also been implemented. This parallel
version is called runiface_parallel()
and is able to launch multiple DREAM
simulations simultaneously. Since the DREAM kernel is not parallelized, this
presents an opportunity to speed up studies requiring multiple independent
simulations to be conducted.
The parallel version of runiface()
takes a list of DREAMSettings
(or
names of files containing such) objects and output filenames as input, both of
which are required arguments. The arguments quiet
and timeout
are also
present in runiface_parallel()
and have the same role on a per-simulation
basis (i.e. if a timeout is imposed, this timeout is enforced for each
individual simulation, and not collectively for all simulations). In addition
to these arguments, it is also possible to specify a list of files to which
stdout
and stderr
data will be piped, as well as the number of DREAM
processes which can be kept running at a time, and the number of threads each
process is allowed to use.
The basic use of runiface_parallel()
is illustrated below:
from DREAM import DREAMSettings
dss = []
output = []
for i in range(10):
# User-defined function returning a
# populated DREAMSettings object:
ds = setup_DREAMSettings(index=i)
dss.append(ds)
output.append(f'output{i}.h5')
# Run simulations in parallel,
# 4 simulations at a time
runiface_parallel(dss, output, quiet=True, njobs=4)
When using runiface_parallel()
all DREAM processes will, by default, print
to the same terminal. The printed information may therefore be very difficult to
interpret, as text from different processes are mixed together. To redirect the
output/error information for each process to separate files, you may use the
stdout_list
and stderr_list
arguments to runiface_parallel()
:
from DREAM import DREAMSettings
dss = []
output = []
stdout, stderr = [], []
for i in range(10):
# User-defined function returning a
# populated DREAMSettings object:
ds = setup_DREAMSettings(index=i)
dss.append(ds)
output.append(f'output{i}.h5')
stdout.append(f'stdout{i}.txt')
stderr.append(f'stderr{i}.txt')
# Run simulations in parallel,
# 4 simulations at a time
runiface_parallel(dss, output, stdout_list=stdout,
stderr_list=stderr, quiet=True, njobs=4)
If provided, stdout_list
and stderr_list
must contain equally many
elements as the dss
and output
lists, i.e. one element per DREAM
simulation.
Note
You can follow the progress of each simulation in real time by reading the
contents of the files. One particularly useful UNIX command for this is
tail
. In the example above, you could follow the progress of the first
process using the command tail -f stdout1.txt
.
Although the DREAM kernel is not parallelized itself, the linear solver in
Intel MKL (going by the name DREAM.Settings.Solver.LINEAR_SOLVER_MKL
in
DREAM) is able to parallelized using threads. In some circumstances, this
parallelization has be observed to severely impact performance of individual
simulations when using runiface_parallel()
to run multiple simulations in
parallel. The cause of this performance penalty seems to be that the linear
solver uses all available threads on the system, forcing expensive task
switching and causing the processes to run serially in practice. To avoid this,
one can provide the argument nthreads
to runiface_parallel()
with the
maximum number of threads allowed for each process. The value provided to this
argument should be such that njobs * nthreads
equals the number of available
CPU cores, for optimal performance.
from DREAM import DREAMSettings
dss = []
output = []
for i in range(10):
# User-defined function returning a
# populated DREAMSettings object:
ds = setup_DREAMSettings(index=i)
dss.append(ds)
output.append(f'output{i}.h5')
# Run simulations in parallel,
# 4 simulations at a time
runiface_parallel(dss, output, njobs=4, nthreads=2)