Associate Professor Mikael Mortensen
Department of Mathematics
Division of Mechanics
University of Oslo
MekIT'17, Trondheim 11/5 - 2017
what? why? where?
Solution
Expansion coefficients
(unknowns)
Test functions
what? why? where?
Solution
Expansion coefficients
(unknowns)
Test functions
Why not? Only simple cartesian domains unless you do spectral elements
what? why? where?
3D + time and HUGE resolution
Serious number crunching - extreme computing
Images from huge spectral DNS simulations by S. de Bruyn Kops, UmassAmherst, College of Engineering
Isn't it possible to do this just as well in a nice language like Python?
What does it really take to build a spectral DNS solver?
Numpy is designed around structured arrays that can be reshaped, resized, sliced, transposed, etc.
Most array operations are performed in C and are very fast
(1) Structured grid
(2) 3D parallel FFT
Numpy wraps FFTpack
pyFFTW wraps FFTW
No MPI
But mpi4py wraps almost the entire MPI library and is as efficient as low-level C
Slab decomposition (max N CPUs)
rfft2(a, axes=(0,1))
fft(a, axis=2)
Real space
Spectral space
4 processors - 4 colours
Do serial FFTs on local data
then transfer and realign datastructures
Do FFT in newly aligned direction
MPI.Alltoall
mpiFFT4py
mpi4py-fft
https://bitbucket.org/mpi4py/mpi4py-fft
With Licandro Dalcin at KAUST
Completely generic implementation - 500 lines of Python code
Both scaling very well on
thousands of CPUs
Enstrophy colored by pressure
which turns out to be very easy, and
100 lines of compact Python code is enough to implement
Approximately 30% slower than C
Easily optimized the last 30% using Cython
Using just Fourier
Chebyshev
Legendre
(1) Choose spectral basis functions satisfying boundary conditions, e.g.:
(2) Transform PDEs using the method of weighted residuals -> variational forms
Best answer: The Spectral Galerkin method
shenfun
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
Solve equations with code very similar to mathematics
However
How to automate?
What do we know about
automating the solution
of PDE'S?
Python package for automating the spectral Galerkin method
for high performance computing on tensor product grids
A FEniCS like interface with a simple form language
Extremely fast solvers for Poisson, Helmholtz and Biharmonic problems
MPI - invisible to the user
Fourier - Chebyshev - Legendre
modified basis functions
Chebfun
Spectral collocation, MATLAB, structured, no MPI
Semtex
Spectral element, MPI, C++/Fortran, 2D unstructured (1 Fourier)
Quite low-level, nowhere near as easily configured as FEniCS for new types of equations.
Very good for Navier-Stokes
Nek5000
Spectral element, Fortran, MPI, 3D unstructured
Nektar++
Spectral hp-element, C++, 3D unstructured
Nothing really for fast development of new spectral solvers aimed at high performance computing on supercomputers
Poisson example
Create a basis that satisfies boundary conditions, e.g.,
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
Combinations of Fourier and Chebyshev/Legendre bases
Any combination of spaces (ultimately)
With solutions of the form:
Unknown expansion coefficients
Combinations of Fourier and Chebyshev/Legendre bases
from shenfun import *
from mpi4py import MPI
comm = MPI.COMM_WORLD
N = (20, 30)
V0 = chebyshev.ShenDirichletBasis(N[0])
V1 = fourier.R2CBasis(N[1])
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:
to make implementation very similar to mathematics
Mathamatical 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
(not in FEniCS)
u_ = V.backward(u_hat, u_)
Any spectral function is defined as
3D:
1D:
This is computed on the tensor product grid as a backwards transform,
e.g., a discrete Fast Fourier inverse transform
A non-periodic basis uses fast Chebyshev transforms
by L2-projections
u_hat = V.forward(u_, u_hat)
which is achieved using a forward transform
Implemented with FFTs or fast Chebyshev transforms
VectorTensorProductSpace
import numpy as np
from shenfun import *
N = 16
K0 = C2CBasis(N)
K1 = C2CBasis(N)
K2 = R2CBasis(N)
T = TensorProductSpace(comm, (K0, K1, K2))
# Scalar function u_
u_ = Function(T, False)
u_hat = Function(T)
u_[:] = np.random.random(u_.shape)
u_hat = T.forward(u_, u_hat)
# Now define vector space to allow computation of grad(u_)
TV = VectorTensorProductSpace([T]*3)
du_hat = project(grad(u_), TV, uh_hat=u_hat)
are all treated with pseudospectral 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))
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)
def compute_rhs(rhs, u_hat, U, Up, T, Tp, w0):
rhs.fill(0)
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
1 sec per time step on 512 cores
Tensor product mesh:
Shaheen II at KAUST
http://github.com/spectralDNS