Solving the advection-diffusion equation $$\frac{\partial c}{\partial t} + \nabla \cdot (\vec{u} c) = \nabla \cdot (\kappa \nabla c) + F$$
t=state.scalar_fields["Tracer"] # Coefficient(FiniteElement("CG", "triangle", 1))
u=state.vector_fields["Velocity"] # Coefficient(VectorElement("CG", "triangle", 1))
p=TrialFunction(t)
q=TestFunction(t)
diffusivity = 0.1
M = p * q * dx
d = dt * (diffusivity * dot(grad(q), grad(p)) - dot(grad(q), u) * p) * dx
a = M + 0.5 * d
L = action(M - 0.5 * d, t)
solve(a == L, t)
What goes on behind the scenes of the solve
call (simplified example!):
from pyop2 import op2, ffc_interface
def solve(equation, x):
# Generate kernels for matrix and rhs assembly
lhs = ffc_interface.compile_form(equation.lhs, "lhs")[0]
rhs = ffc_interface.compile_form(equation.rhs, "rhs")[0]
# Omitted: extract coordinates (coords), connectivity (elem_node)
# and coefficients (tracer t, velocity u)
# Construct OP2 matrix to assemble into
sparsity = op2.Sparsity((elem_node, elem_node), sparsity_dim)
mat = op2.Mat(sparsity, numpy.float64)
b = op2.Dat(nodes, 1, np.zeros(nodes.size))
# Assemble lhs, rhs and solve linear system
op2.par_loop(lhs, elements(3,3),
mat((elem_node[op2.i[0]], elem_node[op2.i[1]]), op2.INC),
coords(elem_node, op2.READ))
op2.par_loop(rhs, elements(3),
b(elem_node[op2.i[0]], op2.INC),
coords(elem_node, op2.READ),
t(elem_node, op2.READ),
u(elem_node, op2.READ))
op2.solve(mat, x, b)
PyOP2: performance portability for any unstructured mesh computations, not limited to FEM!
Use PyOP2 kernel for re-normalising a vector field
vec_norm_code="""
void vec_norm(double *u)
{
const double n = sqrt(u[0]*u[0]+u[1]*u[1]);
u[0] /= n;
u[1] /= n;
}
"""
vec_norm = op2.Kernel(vec_norm_code, "vec_norm")
op2.par_loop(vec_norm, nodes,
u(op2.IdentityMap, op2.RW))
https://code.launchpad.net/~mapdes/ffc/pyop2
https://code.launchpad.net/~fluidity-core/fluidity/floppy_gn
https://github.com/OP2/PyOP2_benchmarks
For each UFL equation in each time step: