The python boutcore module

Installing

Installing boutcore can be tricky. Ideally it should be just

./configure --enable-shared
make -j 4 python

but getting all the dependencies can be difficult. make python creates the python3 module.

If problems arise, it might be worth checking a copy of the bout module out, to reduce the risk of causing issues with the old bout installation. This is especially true if you are trying to run boutcore not on compute nodes of a super computer but rather on post-processing/login/… nodes.

To use boutcore on the login node, a self compiled version of mpi may be required, as the provided one may be only for the compute nodes. Further, numpy header files are required, therefore numpy needs to be compiled as well. Further, the header files need to be exposed to the boutcore cython compilation, e.g. by adding them to _boutcore_build/boutcore.pyx.in. It seems both NUMPY/numpy/core/include and NUMPY/build/src.linux-x86_64-2.7/numpy/core/include/numpy need to be added, where NUMPY is the path of the numpy directory. For running boutcore on the post processing nodes, fftw3 needs to be compiled as well, if certain fftw routines are used. Note, fftw needs to be configured with --enable-shared.

After installing mpi e.g. in ~/local/mpich, bout needs to be configured with something like: ./configure --enable-shared MPICC=~/local/mpich/bin/mpicc MPICXX=~/local/mpich/bin/mpicxx --with-fftw=~/local/fftw/

--enable-shared is required, so that pvode etc. is compiles as position independent code.

If you are running fedora - you can install pre-build binaries:

sudo dnf copr enable davidsch/bout
sudo dnf install python3-bout++-mpich
module load mpi/mpich-$(arch)

Purpose

The boutcore module exposes (part) of the BOUT++ C++ library to python. It allows to calculate e.g. BOUT++ derivatives in python.

State

Field3D and Field2D are working. If other fields are needed, please open an issue. Fields can be accessed directly using the [] operators, and give a list of slice objects. The get all data, f3d.getAll() is equivalent to f3d[:,:,] and returns a numpy array. This array can be addressed with e.g. [] operators, and then the field can be set again with f3d.setAll(numpyarray). It is also possible to set a part of an Field3D with the [] operators. Addition, multiplication etc. are all available. The derivatives should all be working, if find a missing one, please open an issue. Vectors are not exposed yet.

Functions

See the API documentation boutcore package

Examples

Some trivial post processing:

import boutcore
import numpy as np
args="-d data -f BOUT.settings -o BOUT.post".split(" ")
boutcore.init(args)
dens=boutcore.Field3D.fromCollect("n",path="data")
temp=boutcore.Field3D.fromCollect("T",path="data")
pres=dens*temp
dpdz=boutcore.DDZ(pres,outloc="CELL_ZLOW")

A simple MMS test:

import boutcore
import numpy as np
boutcore.init("-d data -f BOUT.settings -o BOUT.post")
for nz in [64,128,256]:
    boutcore.setOption("meshz:nz","%d"%nz)
    mesh=boutcore.Mesh(OptionSection="meshz")
    f=boutcore.create3D("sin(z)",mesh)
    sim=boutcore.DDZ(f)
    ana=boutcore.create3D("cos(z)",mesh)
    err=sim-ana
    err=boutcore.max(boutcore.abs(err))
    errors.append(err)

A real example - unstagger data:

import boutcore
boutcore.init("-d data -f BOUT.settings -o BOUT.post")
# uses location from dump - is already staggered
upar=boutcore.Field3D.fromCollect("Upar")
upar=boutcore.interp_to(upar,"CELL_CENTRE")
# convert to numpy array
upar=upar.getAll()

A real example - check derivative contributions:

#!/usr/bin/env python

from boutcore import *
import numpy as np
from netCDF4 import Dataset
import sys

if len(sys.argv)> 1:
    path=sys.argv[1]
else:
    path="data"

times=collect("t_array",path=path)

boutcore.init("-d data -f BOUT.settings -o BOUT.post")
with Dataset(path+'/vort.nc', 'w', format='NETCDF4') as outdmp:
   phiSolver=Laplacian()
   phi=Field3D.fromCollect("n",path=path,tind=0,info=False)
   zeros=phi.getAll()*0
   phi.setAll(zeros)
   outdmp.createDimension('x',zeros.shape[0])
   outdmp.createDimension('y',zeros.shape[1])
   outdmp.createDimension('z',zeros.shape[2])
   outdmp.createDimension('t',None)
   t_array_=outdmp.createVariable('t_array','f4',('t'))
   t_array_[:]=times
   ExB     = outdmp.createVariable('ExB'    ,'f4',('t','x','y','z'))
   par_adv = outdmp.createVariable('par_adv','f4',('t','x','y','z'))
   def setXGuards(phi,phi_arr):
       for z in range(tmp.shape[2]):
           phi[0,:,z]=phi_arr
           phi[1,:,z]=phi_arr
           phi[-2,:,z]=phi_arr
           phi[-1,:,z]=phi_arr
   with open(path+"/equilibrium/phi_eq.dat","rb") as inf:
       phi_arr=np.fromfile(inf,dtype=np.double)
       bm="BRACKET_ARAKAWA_OLD"

       for tind in range(len(times)):
           vort     = Field3D.fromCollect("vort"     ,path=path,tind=tind,info=False)
           U        = Field3D.fromCollect("U"        ,path=path,tind=tind,info=False)
           setXGuards(phi,phi_arr)
           phi=phiSolver.solve(vort,phi)
           ExB[tind,:,:,:]=(-bracket(phi, vort, bm, "CELL_CENTRE")).getAll()
           par_adv[tind,:,:,:]=(- Vpar_Grad_par(U, vort)).getAll()