Mikael Mortensen, Miroslav Kuchta
Department of Mathematics, University of Oslo
and
Lisandro Dalcin
King Abdullah University of Science and Technology
International Conference on Computational Science and Engineering, Simula 23/10 - 2017
Started out as a toy project for
myself and Hans Petter
Using Fourier transformed Navier Stokes equations
We knew Direct Numerical Simulations (DNS) of turbulence were performed with low-level Fortran or C
From scratch?
(Not just wrapping a Fortran solver)
Using only standard Python modules numpy/scipy/mpi4py?
Can we make this solver run on thousands of cores ?
With billions of unknowns?
As fast as C/fortran?
65,536 cores!
Gist
and Hans Petter is off to write 4 new books,
but with new collaborators, and fast transforms in place we can do so much more
mpi4py-fft
https://bitbucket.org/mpi4py/mpi4py-fft
With Lisandro Dalcin at KAUST
~ 500 lines of Python code
With the newly released
we can do any transform from FFTW in parallel
Highly configurable and fast
on non-periodic domains, with different types
of boundary conditions (Dirichlet/Neumann)
Not just periodic Fourier
Choose global basis (test) functions satisfying the correct boundary conditions. Usually modified Chebyshev or Legendre polynomials, or Fourier exponentials
Transform PDEs to weak variational forms using the method of weighted residuals
Assemble and solve system of equations
Solution
Expansion coefficients
(unknowns)
Global basis functions
and it works in three steps:
Poisson example
1) Create a basis that satisfies boundary conditions, e.g.,
(Legendre polynomial order k+2)
2) Transform PDE to variational form using test function
Trial function
Test function
With quadrature notation
from fenics import *
mesh = UnitSquareMesh(8, 8)
V = FunctionSpace(mesh, 'CG', 1)
# Define variational problem
u = TrialFunction(V)
v = TestFunction(V)
f = Constant(-6.0)
a = inner(grad(u), grad(v))*dx
L = f*v*dx
Finite accuracy (not spectral)
How?
https://github.com/spectralDNS/shenfun
Python package (~3k loc) for automating the spectral Galerkin
method for high performance computing on tensor product grids
import numpy as np
from shenfun import TestFunction, TrialFunction, inner, div, grad, chebyshev
N = 20
V = chebyshev.ShenDirichletBasis(N, plan=True)
v = TestFunction(V)
u = TrialFunction(V)
# Assemble stiffness matrix
A = inner(v, div(grad(u)))
# Some consistent right hand side
f = Function(V, False)
f[:] = -2
# Assemble right hand side
f_hat = inner(v, f)
# Solve equations and transform spectral solution to real space
f_hat = A.solve(f_hat)
fj = V.backward(f_hat)
x = V.mesh(N)
assert np.allclose(fj, 1-x**2)
Very similar to FEniCS!
just with spectral accuracy
(1) Create basis
(2) Variational forms
(3) Solve system
Cartesian products of Fourier and Chebyshev/Legendre bases
Any Cartesian combination of spaces (ultimately)
With solutions of the form:
Unknown expansion coefficients
Implementation
from shenfun import *
from mpi4py import MPI
comm = MPI.COMM_WORLD
N = (20, 30)
# Create two bases
V0 = chebyshev.ShenDirichletBasis(N[0])
V1 = fourier.R2CBasis(N[1])
# Create tensor product space
T = TensorProductSpace(comm, (V0, V1))
v = TestFunction(T)
u = TrialFunction(T)
A = inner(v, div(grad(u)))
# Or 3D
V2 = fourier.C2CBasis(40)
T3 = TensorProductSpace(comm, (V0, V2, V1))
v = TestFunction(T3)
u = TrialFunction(T3)
A = inner(v, div(grad(u)))
MPI under the hood
using mpi4py-fft
on tensor product grids like:
Slab
Pencil
influenced heavily by UFL/FEniCS
Mathematical operators act on test, trial, or functions with data
V = TensorProductSpace(comm, (V0, V1, V2))
# Form arguments:
v = TestFunction(V)
u = TrialFunction(V)
u_ = Function(V, False)
# Forms
du = grad(u_)
d2u = div(du)
cu = curl(du)
dudx = Dx(u_, 0)
Inner products and projections
A = inner(v, u) # Bilinear form, assembles to matrices
f = inner(v, u_) # Linear form, assembles to vector
project(div(grad(u_)), V) # projection to V
u_hat = V.forward(u, u_hat) u = V.backward(u_hat, u)
Move back and forth between and using fast transforms
Periodic basis using fast Fourier transforms
Non-periodic basis using fast Chebyshev transforms (not for Legendre)
all treated with pseudo-spectral techniques
(1) Compute inverse
u = T.backward(u_hat)
with dealiasing using
3/2-rule (padding) or 2/3-rule
(3) Transform back to spectral space
w = u*u
(2) Compute nonlinearity
w_hat = T.forward(w)
T = TensorProductSpace(comm, (V0, V1, V2))
Fourier: A is diagonal
Modified Legendre: A is diagonal
Modified Chebyshev: A sparse - solved as tridiagonal matrix
2 non-periodic directions
Matrix form:
Sparse matrices A (stiffness) and B (mass) are 1D matrices of basis
Very fast direct solvers implemented in parallel using matrix decomposition method
Linear algebra:
example
2D periodic (x, y) \( \in [-50, 50]^2 \)
Use Fourier in both directions
Weak form, only spatial discretization:
example
2D periodic (x, y) \( \in [-50, 50]^2 \)
import sympy
from shenfun.fourier.bases import R2CBasis, C2CBasis
from shenfun import inner, grad, TestFunction, TrialFunction, \
Function, TensorProductSpace
# Use sympy to set up initial condition
x, y = sympy.symbols("x,y")
ue = (x + y)*sympy.exp(-0.03*(x**2+y**2))
N = (200, 200)
K0 = C2CBasis(N[0], domain=(-50., 50.))
K1 = C2CBasis(N[1], domain=(-50., 50.))
T = TensorProductSpace(comm, (K0, K1))
u = TrialFunction(T)
v = TestFunction(T)
U = Function(T, False)
def compute_rhs(rhs, u_hat, U, T):
rhs = inner(grad(v), -grad(U), output_array=rhs, uh_hat=u_hat)
rhs /= (2*np.pi)**2
rhs += u_hat
rhs -= T.forward((1+1.5j)*U*abs(U)**2, w0)
return rhs
# And, e.g., Runge-Kutta integrator
#
#
Key code:
Turbulent channel flow
3D tensor product space
2 Fourier bases +
Modified Chebyshev bases for wall normal direction
Kim and Moin modified version of Navier-Stokes:
is convection
Wall normal velocity
Wall normal vorticity
Turbulent channel flow
First fully spectral DNS with
3D Tensor product mesh:
Shaheen II at KAUST
http://github.com/spectralDNS/shenfun