hydro by example

A simple python-based tutorial on computational methods for hydrodynamics









low Mach





∗ pyro's design

pyro is written primarily in python, with a few low-level routines written in Fortran for performance. The numpy package is used for representing arrays throughout the python code and the matplotlib library is used for visualization. We use f2py (part of NumPy) to interface with some Fortran code. Finally, pytest is used for unit testing of some components.

All solvers are written for a 2-d grid.

A paper describing the design philosophy of pyro was accepted to Astronomy & Computing.

pyro paper download

Note: by default, pyro uses python3.

Directory structure

The files for each solver are in their own sub-directory, with additional sub-directories for the mesh and utilities. Each solver has two sub-directories: problems and tests. These store the different problem setups for the solver and reference output for testing.

Your PYTHONPATH environment variable should be set to include the top-level pyro2/ directory.

The overall structure is:


Fortran is used to speed up some critical portions of the code, and in often cases, provides more clarity than trying to write optimized python code using array operations in numpy. The Fortran code seemlessly integrates into python using f2py.

Wherever Fortran is used, we enforce the following design rule: the Fortran functions must be completely self-contained, with all information coming through the interface. Now external dependencies are allowed. Each pyro module will have (at most) a single Fortran file and can be compiled into a library via a single f2py command line invocation.

A single script, mk.sh, in the top-level directory will compile all the Fortran source.

∗ Main driver

All the solvers use the same driver, the main pyro.py script. The flowchart for the driver is:

This format is flexible enough for the advection, compressible, diffusion, and incompressible evolution solver. Each solver provides a Simulation class that provides the following methods (note: inheritance is used, so many of these methods come from the base NullSimulation class):

Each problem setup needs only provide an init_data() function that fills the data in the patch object.


All the solvers are run through the pyro.py script. This takes 3 arguments: the solver name, the problem setup to run with that solver (this is defined in the solver's problems/ sub-directory), and the inputs file (again, usually from the solver's problems/ directory).

For example, to run the Sedov problem with the compressible solver we would do:

./pyro.py compressible sedov inputs.sedov
This knows to look for inputs.sedov in compressible/problems/ (alternately, you can specify the full path for the inputs file).

To run the smooth Gaussin advection problem with the advection solver, we would do:

./pyro.py advection smooth inputs.smooth

Any runtime parameter can also be specified on the command line, after the inputs file. For example, to disable runtime visualization for the above run, we could do:

./pyro.py advection smooth inputs.smooth vis.dovis=0

∗ Runtime options

The behavior of the main driver, the solver, and the problem setup can be controlled by runtime parameters specified in the inputs file (or via the command line). Runtime parameters are grouped into sections, with the heading of that section enclosed in "[ .. ]". The list of parameters are stored in the pyro/_defaults file, the _defaults files in the solver directory, and the _problem-name.defaults file in the solver's problem/ sub-directory. These three files are parsed at runtime to define the list of valid parameters. The inputs file is read next and used to override the default value of any of these previously defined parameters. Additionally, any parameter can be specified at the end of the commandline, and these will be used to override the defaults. The collection of runtime parameters is stored in a RuntimeParameters object.

The runparams.py module in util/ controls access to the runtime parameters. You can setup the runtime parameters, parse an inputs file, and access the value of a parameter (hydro.cfl in this example) as:

rp = RuntimeParameters()
cfl = rp.get_param("hydro.cfl")

When pyro is run, the file inputs.auto is output containing the full list of runtime parameters, their value for the simulation, and the comment that was associated with them from the _defaults files. This is a useful way to see what parameters are in play for a given simulation.

All solvers use the following parameters:

max_steps the maximum number of steps in the simulation
tmax the simulation time to evolve to
init_tstep_factor the amount by which to shrink the first timestep. This lets the code ramp up to the CFL timestep slowly
max_dt_change the maximum factor by which the timestep can increase from one step to the next
basename the descriptive prefix to use for output files
dt_out the interval in simulation time between writing output files
n_out the number of timesteps between writing output files
dovis enable (1) or disable (0) runtime visualization
store_images if 1, write out PNG files as we do the runtime visualization
n_out the number of timesteps between writing output files
xmin the physical coordinate of the lower x face of the domain
xmax the physical coordinate of the upper x face of the domain
ymin the physical coordinate of the lower y face of the domain
ymax the physical coordinate of the upper y face of the domain
xlboundary the physical description for the type of boundary at the lower x face of the domain
xrboundary the physical description for the type of boundary at the upper x face of the domain
ylboundary the physical description for the type of boundary at the lower y face of the domain
yrboundary the physical description for the type of boundary at the upper y face of the domain
nx the number zones in the x-direction
ny the number zones in the y-direction

∗ Utilities

Several simply utilities exist to operate on output files

∗ Working with output

pyro data can be read using the patch.read method. The following sequence (done in a python session) reads in stored data (from the compressible Sedov problem) and plots data falling on a line in the x direction through the y-center of the domain (note: this will include the ghost cells).

import matplotlib.pyplot as plt
import util.io as io
sim = io.read("sedov_unsplit_0000.h5")
dens = sim.cc_data.get_var("density")
plt.plot(dens.g.x, dens[:,dens.g.ny//2])

∗ Adding a problem

The easiest way to add a problem is to copy an existing problem setup in the solver you wish to use (in its problems/ sub-directory. Three different files will need to be copied (created):

Once the problem is defined, you need to add the problem name to the __all__ list in the __init__.py file in the problems/ sub-directory. This lets python know about the problem.