Thanks @dokken for the quick reply.
Below is the code (I tried to simplify as much as I could, so it’s a non-physical problem). The last three lines are the important ones. You can see that assembling the vectors V1 and V2 at every iteration is what is taking most of the computation time.
import numpy as np
from mpi4py import MPI
import dolfinx
from dolfinx.fem import (Constant, Function, FunctionSpace, apply_lifting, assemble_matrix,
assemble_scalar, assemble_vector, locate_dofs_topological, set_bc,
dirichletbc, form)
from dolfinx import fem
from dolfinx import io
from ufl import (inner, div, dx, nabla_grad, sym, Identity, as_tensor, Dn, grad, ds, rhs, lhs, Measure, transpose, as_tensor,
dot, FacetNormal, indices)
from petsc4py import PETSc
import ufl
rank = MPI.COMM_WORLD.rank
# Parameters
L = 1
num_steps = 2000 # number of steps
dt = 1e-3 # time step
T = num_steps*dt # simulation time
Re = 10000
gdim = 2
# Initialise meshing process
import gmsh
gmsh.initialize()
ms = 0.1
# Geometry
if rank == 0:
# Points
gmsh.model.occ.addPoint(0, 0, 0, ms, 101)
gmsh.model.occ.addPoint(L, 0, 0, ms, 102)
gmsh.model.occ.addPoint(L, L, 0, ms, 103)
gmsh.model.occ.addPoint(0, L, 0, ms, 104)
gmsh.model.occ.addLine(101, 102, 1001)
gmsh.model.occ.addLine(102, 103, 1002)
gmsh.model.occ.addLine(103, 104, 1003)
gmsh.model.occ.addLine(104, 101, 1004)
gmsh.model.occ.addCurveLoop([1001, 1002, 1003, 1004], 50001)
gmsh.model.occ.addPlaneSurface([50001], tag=500001)
gmsh.model.occ.synchronize()
# Domain markers
fluid_marker = 1
if rank == 0:
volumes = gmsh.model.getEntities(dim=gdim)
gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")
# Generate mesh
if rank == 0:
gmsh.model.mesh.generate(gdim)
gmsh.model.mesh.optimize("Netgen")
# Create Dolfinx mesh and facet tags
from dolfinx.io import (VTXWriter, distribute_entity_data, gmshio)
mesh, cell_markers, ft = io.gmshio.model_to_mesh(gmsh.model, MPI.COMM_WORLD, 0, gdim=gdim)
# Define normal vector
n = FacetNormal(mesh)
''' Define function spaces '''
from ufl import VectorElement, FiniteElement, MixedElement, TrialFunctions, TestFunctions
# Velocity element space
v_cg2 = VectorElement('Lagrange', mesh.ufl_cell(), 2) # Order 2
V = FunctionSpace(mesh, v_cg2)
# Pressure element space
p_cg1 = FiniteElement('Lagrange', mesh.ufl_cell(), 1) # Order 1
Q = FunctionSpace(mesh, p_cg1)
# Mixed element space
mel = MixedElement([v_cg2, p_cg1])
Y = FunctionSpace(mesh,mel)
# Trial and test functions
u, p = TrialFunctions(Y)
v, q = TestFunctions(Y)
# Create initial condition
q_n = Function(Y)
q_n.name = "q_n"
p_n = q_n.sub(1)
p_n.name = "p_n"
u_n = q_n.sub(0)
u_n.name = "u_n"
# Create facet to cell connectivity required to determine boundary facets
tdim = mesh.topology.dim
fdim = tdim - 1
mesh.topology.create_connectivity(fdim, tdim)
boundary_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
# Boundary conditions
u_D_W = fem.Function(Y).sub(0)
bc = [fem.dirichletbc(u_D_W, fem.locate_dofs_topological(Y.sub(0), fdim, boundary_facets))]
# Stress tensor
def tao(u, Re):
return ( (ufl.grad(u) + ufl.transpose(ufl.grad(u)) - fem.Constant(mesh,2.0/3.0)*ufl.Identity(u.geometric_dimension())*ufl.div(u))/Re )
def sigma(u, p, Re):
return (ufl.grad(u) + ufl.transpose(ufl.grad(u)) - fem.Constant(mesh,2.0/3.0)*ufl.Identity(u.geometric_dimension())*ufl.div(u))/Re - p*ufl.Identity(u.geometric_dimension())
# Numerical parameters
k = Constant(mesh, dt)
# Variational Form
T_comp = ufl.inner(u,v)*ufl.dx
T_comp += ufl.inner(p,q)*ufl.dx
S_comp = ufl.inner(p,ufl.div(v))*ufl.dx
S_comp -= ufl.inner(tao(u,Re),ufl.grad(v))*ufl.dx
S_comp -= ufl.inner(ufl.div(u),q)*ufl.dx
B_comp = ufl.inner(ufl.dot(sigma(u,p,Re),n),v)*ufl.ds
T_comp_old = ufl.inner(u_n,v)*ufl.dx
T_comp_old += ufl.inner(p_n,q)*ufl.dx
S_comp_old = ufl.inner(p_n,ufl.div(v))*ufl.dx
S_comp_old -= ufl.inner(tao(u_n,Re),ufl.grad(v))*ufl.dx
S_comp_old -= ufl.inner(ufl.div(u_n),q)*ufl.dx
B_comp_old = ufl.inner(ufl.dot(sigma(u_n,p_n,Re),n),v)*ufl.ds
Form = (1/k)*(T_comp - T_comp_old) - (1/2)*(S_comp + S_comp_old) - (1/2)*(B_comp + B_comp_old)
# Bilinear and linear form
a = fem.form(ufl.lhs(Form))
L = fem.form(ufl.rhs(Form))
# Assemble left-hand side matrix A and right-hand side vector b
A = fem.petsc.assemble_matrix(a, bcs=bc)
A.assemble()
b = fem.petsc.create_vector(L)
# Linear system solver
solver = PETSc.KSP().create(mesh.comm)
solver.setOperators(A)
solver.setType(PETSc.KSP.Type.GMRES)
import tqdm.notebook
progress = tqdm.tqdm(desc="Solving PDE", total=num_steps)
t = 0
for iterations in range(num_steps):
progress.update(1)
t += dt
# Update the right hand side reusing the initial vector
with b.localForm() as loc_b:
loc_b.set(0)
fem.petsc.assemble_vector(b, L)
# Apply Dirichlet boundary condition to the vector
apply_lifting(b, [a], [bc])
b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b, bc)
# Solve linear problem
solver.solve(b, q_n.vector)
q_n.x.scatter_forward()
u_n.x.scatter_forward()
p_n.x.scatter_forward()
# Define vectors
V1 = fem.petsc.assemble_vector( fem.form(ufl.dot(ufl.dot(sigma(u_n,p_n,Re),n),v)*ufl.ds) )
V2 = fem.petsc.assemble_vector( fem.form(ufl.dot(n ,v)*ufl.ds) )
# Compute solution
Solution = V1.dot(V2)